box-ui-elements 23.4.0-beta.20 → 23.4.0-beta.22

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.
@@ -18,13 +18,62 @@ const SidebarNavButton = /*#__PURE__*/React.forwardRef((props, ref) => {
18
18
  'data-testid': dataTestId,
19
19
  children,
20
20
  elementId = '',
21
+ internalSidebarNavigation,
22
+ internalSidebarNavigationHandler,
21
23
  isDisabled,
22
24
  isOpen,
23
25
  onClick = noop,
26
+ routerDisabled = false,
24
27
  sidebarView,
25
28
  tooltip
26
29
  } = props;
27
30
  const sidebarPath = `/${sidebarView}`;
31
+ const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
32
+ if (routerDisabled) {
33
+ // Mimic router behavior using internalSidebarNavigation
34
+ const isMatch = !!internalSidebarNavigation && internalSidebarNavigation.sidebar === sidebarView;
35
+ const isActiveValue = isMatch && !!isOpen;
36
+
37
+ // Mimic isExactMatch: true when no extra navigation parameters are present
38
+ const hasExtraParams = internalSidebarNavigation && (internalSidebarNavigation.versionId || internalSidebarNavigation.activeFeedEntryType || internalSidebarNavigation.activeFeedEntryId || internalSidebarNavigation.fileVersionId);
39
+ const isExactMatch = isMatch && !hasExtraParams;
40
+ const handleNavButtonClick = event => {
41
+ onClick(sidebarView);
42
+
43
+ // Mimic router navigation behavior
44
+ if (internalSidebarNavigationHandler && !event.defaultPrevented && isLeftClick(event)) {
45
+ const replace = isExactMatch;
46
+ internalSidebarNavigationHandler({
47
+ sidebar: sidebarView,
48
+ open: true
49
+ }, replace);
50
+ }
51
+ };
52
+ return /*#__PURE__*/React.createElement(Tooltip, {
53
+ position: "middle-left",
54
+ text: tooltip,
55
+ isTabbable: false
56
+ }, /*#__PURE__*/React.createElement(Button, {
57
+ accessibleWhenDisabled: true,
58
+ "aria-controls": `${id}-content`,
59
+ "aria-label": tooltip,
60
+ "aria-selected": isActiveValue,
61
+ className: classNames('bcs-NavButton', {
62
+ 'bcs-is-selected': isActiveValue,
63
+ 'bdl-is-disabled': isDisabled
64
+ }),
65
+ "data-resin-target": dataResinTarget,
66
+ "data-testid": dataTestId,
67
+ ref: ref,
68
+ id: id,
69
+ disabled: isDisabled,
70
+ onClick: handleNavButtonClick,
71
+ role: "tab",
72
+ tabIndex: isActiveValue ? '0' : '-1',
73
+ type: "button",
74
+ variant: "tertiary"
75
+ }, children));
76
+ }
28
77
  return /*#__PURE__*/React.createElement(Route, {
29
78
  path: sidebarPath
30
79
  }, ({
@@ -34,7 +83,6 @@ const SidebarNavButton = /*#__PURE__*/React.forwardRef((props, ref) => {
34
83
  const isMatch = !!match;
35
84
  const isActiveValue = isMatch && !!isOpen;
36
85
  const isExactMatch = isMatch && match.isExact;
37
- const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
38
86
  const handleNavButtonClick = event => {
39
87
  onClick(sidebarView);
40
88
  if (!event.defaultPrevented && isLeftClick(event)) {
@@ -11,6 +11,7 @@ import classNames from 'classnames';
11
11
  import { Button } from '@box/blueprint-web';
12
12
  import Tooltip from '../../components/tooltip/Tooltip';
13
13
  import { isLeftClick } from '../../utils/dom';
14
+ import type { InternalSidebarNavigation, InternalSidebarNavigationHandler, ViewTypeValues } from '../common/types/SidebarNavigation';
14
15
  import './SidebarNavButton.scss';
15
16
 
16
17
  type Props = {
@@ -18,10 +19,13 @@ type Props = {
18
19
  'data-testid'?: string,
19
20
  children: React.Node,
20
21
  elementId?: string,
22
+ internalSidebarNavigation?: InternalSidebarNavigation,
23
+ internalSidebarNavigationHandler?: InternalSidebarNavigationHandler,
21
24
  isDisabled?: boolean,
22
25
  isOpen?: boolean,
23
- onClick?: (sidebarView: string) => void,
24
- sidebarView: string,
26
+ onClick?: (sidebarView: ViewTypeValues) => void,
27
+ routerDisabled?: boolean,
28
+ sidebarView: ViewTypeValues,
25
29
  tooltip: React.Node,
26
30
  };
27
31
 
@@ -31,13 +35,72 @@ const SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props,
31
35
  'data-testid': dataTestId,
32
36
  children,
33
37
  elementId = '',
38
+ internalSidebarNavigation,
39
+ internalSidebarNavigationHandler,
34
40
  isDisabled,
35
41
  isOpen,
36
42
  onClick = noop,
43
+ routerDisabled = false,
37
44
  sidebarView,
38
45
  tooltip,
39
46
  } = props;
40
47
  const sidebarPath = `/${sidebarView}`;
48
+ const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
49
+
50
+ if (routerDisabled) {
51
+ // Mimic router behavior using internalSidebarNavigation
52
+ const isMatch = !!internalSidebarNavigation && internalSidebarNavigation.sidebar === sidebarView;
53
+ const isActiveValue = isMatch && !!isOpen;
54
+
55
+ // Mimic isExactMatch: true when no extra navigation parameters are present
56
+ const hasExtraParams = internalSidebarNavigation && (
57
+ internalSidebarNavigation.versionId ||
58
+ internalSidebarNavigation.activeFeedEntryType ||
59
+ internalSidebarNavigation.activeFeedEntryId ||
60
+ internalSidebarNavigation.fileVersionId
61
+ );
62
+ const isExactMatch = isMatch && !hasExtraParams;
63
+
64
+ const handleNavButtonClick = event => {
65
+ onClick(sidebarView);
66
+
67
+ // Mimic router navigation behavior
68
+ if (internalSidebarNavigationHandler && !event.defaultPrevented && isLeftClick(event)) {
69
+ const replace = isExactMatch;
70
+ internalSidebarNavigationHandler({
71
+ sidebar: sidebarView,
72
+ open: true,
73
+ }, replace);
74
+ }
75
+ };
76
+
77
+ return (
78
+ <Tooltip position="middle-left" text={tooltip} isTabbable={false}>
79
+ <Button
80
+ accessibleWhenDisabled={true}
81
+ aria-controls={`${id}-content`}
82
+ aria-label={tooltip}
83
+ aria-selected={isActiveValue}
84
+ className={classNames('bcs-NavButton', {
85
+ 'bcs-is-selected': isActiveValue,
86
+ 'bdl-is-disabled': isDisabled,
87
+ })}
88
+ data-resin-target={dataResinTarget}
89
+ data-testid={dataTestId}
90
+ ref={ref}
91
+ id={id}
92
+ disabled={isDisabled}
93
+ onClick={handleNavButtonClick}
94
+ role="tab"
95
+ tabIndex={isActiveValue ? '0' : '-1'}
96
+ type="button"
97
+ variant="tertiary"
98
+ >
99
+ {children}
100
+ </Button>
101
+ </Tooltip>
102
+ );
103
+ }
41
104
 
42
105
  return (
43
106
  <Route path={sidebarPath}>
@@ -45,7 +108,6 @@ const SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props,
45
108
  const isMatch = !!match;
46
109
  const isActiveValue = isMatch && !!isOpen;
47
110
  const isExactMatch = isMatch && match.isExact;
48
- const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
49
111
 
50
112
  const handleNavButtonClick = event => {
51
113
  onClick(sidebarView);
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarNavButton.js","names":["React","Route","noop","classNames","Button","Tooltip","isLeftClick","SidebarNavButton","forwardRef","props","ref","dataResinTarget","dataTestId","children","elementId","isDisabled","isOpen","onClick","sidebarView","tooltip","sidebarPath","createElement","path","match","history","isMatch","isActiveValue","isExactMatch","isExact","id","handleNavButtonClick","event","defaultPrevented","method","replace","push","pathname","state","open","position","text","isTabbable","accessibleWhenDisabled","className","disabled","role","tabIndex","type","variant"],"sources":["../../../src/elements/content-sidebar/SidebarNavButton.js"],"sourcesContent":["/**\n * @flow\n * @file Preview sidebar nav button component\n * @author Box\n */\n\nimport * as React from 'react';\nimport { Route } from 'react-router-dom';\nimport noop from 'lodash/noop';\nimport classNames from 'classnames';\nimport { Button } from '@box/blueprint-web';\nimport Tooltip from '../../components/tooltip/Tooltip';\nimport { isLeftClick } from '../../utils/dom';\nimport './SidebarNavButton.scss';\n\ntype Props = {\n 'data-resin-target'?: string,\n 'data-testid'?: string,\n children: React.Node,\n elementId?: string,\n isDisabled?: boolean,\n isOpen?: boolean,\n onClick?: (sidebarView: string) => void,\n sidebarView: string,\n tooltip: React.Node,\n};\n\nconst SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props, ref: React.Ref<any>) => {\n const {\n 'data-resin-target': dataResinTarget,\n 'data-testid': dataTestId,\n children,\n elementId = '',\n isDisabled,\n isOpen,\n onClick = noop,\n sidebarView,\n tooltip,\n } = props;\n const sidebarPath = `/${sidebarView}`;\n\n return (\n <Route path={sidebarPath}>\n {({ match, history }) => {\n const isMatch = !!match;\n const isActiveValue = isMatch && !!isOpen;\n const isExactMatch = isMatch && match.isExact;\n const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;\n\n const handleNavButtonClick = event => {\n onClick(sidebarView);\n\n if (!event.defaultPrevented && isLeftClick(event)) {\n const method = isExactMatch ? history.replace : history.push;\n method({\n pathname: sidebarPath,\n state: { open: true },\n });\n }\n };\n\n return (\n <Tooltip position=\"middle-left\" text={tooltip} isTabbable={false}>\n <Button\n accessibleWhenDisabled={true}\n aria-controls={`${id}-content`}\n aria-label={tooltip}\n aria-selected={isActiveValue}\n className={classNames('bcs-NavButton', {\n 'bcs-is-selected': isActiveValue,\n 'bdl-is-disabled': isDisabled,\n })}\n data-resin-target={dataResinTarget}\n data-testid={dataTestId}\n ref={ref}\n id={id}\n disabled={isDisabled}\n onClick={handleNavButtonClick}\n role=\"tab\"\n tabIndex={isActiveValue ? '0' : '-1'}\n type=\"button\"\n variant=\"tertiary\"\n >\n {children}\n </Button>\n </Tooltip>\n );\n }}\n </Route>\n );\n});\n\nexport default SidebarNavButton;\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,KAAK,QAAQ,kBAAkB;AACxC,OAAOC,IAAI,MAAM,aAAa;AAC9B,OAAOC,UAAU,MAAM,YAAY;AACnC,SAASC,MAAM,QAAQ,oBAAoB;AAC3C,OAAOC,OAAO,MAAM,kCAAkC;AACtD,SAASC,WAAW,QAAQ,iBAAiB;AAC7C,OAAO,yBAAyB;AAchC,MAAMC,gBAAgB,gBAAGP,KAAK,CAACQ,UAAU,CAAwB,CAACC,KAAY,EAAEC,GAAmB,KAAK;EACpG,MAAM;IACF,mBAAmB,EAAEC,eAAe;IACpC,aAAa,EAAEC,UAAU;IACzBC,QAAQ;IACRC,SAAS,GAAG,EAAE;IACdC,UAAU;IACVC,MAAM;IACNC,OAAO,GAAGf,IAAI;IACdgB,WAAW;IACXC;EACJ,CAAC,GAAGV,KAAK;EACT,MAAMW,WAAW,GAAG,IAAIF,WAAW,EAAE;EAErC,oBACIlB,KAAA,CAAAqB,aAAA,CAACpB,KAAK;IAACqB,IAAI,EAAEF;EAAY,GACpB,CAAC;IAAEG,KAAK;IAAEC;EAAQ,CAAC,KAAK;IACrB,MAAMC,OAAO,GAAG,CAAC,CAACF,KAAK;IACvB,MAAMG,aAAa,GAAGD,OAAO,IAAI,CAAC,CAACT,MAAM;IACzC,MAAMW,YAAY,GAAGF,OAAO,IAAIF,KAAK,CAACK,OAAO;IAC7C,MAAMC,EAAE,GAAG,GAAGf,SAAS,GAAGA,SAAS,KAAK,EAAE,GAAG,EAAE,GAAG,GAAG,GAAGI,WAAW,EAAE;IAErE,MAAMY,oBAAoB,GAAGC,KAAK,IAAI;MAClCd,OAAO,CAACC,WAAW,CAAC;MAEpB,IAAI,CAACa,KAAK,CAACC,gBAAgB,IAAI1B,WAAW,CAACyB,KAAK,CAAC,EAAE;QAC/C,MAAME,MAAM,GAAGN,YAAY,GAAGH,OAAO,CAACU,OAAO,GAAGV,OAAO,CAACW,IAAI;QAC5DF,MAAM,CAAC;UACHG,QAAQ,EAAEhB,WAAW;UACrBiB,KAAK,EAAE;YAAEC,IAAI,EAAE;UAAK;QACxB,CAAC,CAAC;MACN;IACJ,CAAC;IAED,oBACItC,KAAA,CAAAqB,aAAA,CAAChB,OAAO;MAACkC,QAAQ,EAAC,aAAa;MAACC,IAAI,EAAErB,OAAQ;MAACsB,UAAU,EAAE;IAAM,gBAC7DzC,KAAA,CAAAqB,aAAA,CAACjB,MAAM;MACHsC,sBAAsB,EAAE,IAAK;MAC7B,iBAAe,GAAGb,EAAE,UAAW;MAC/B,cAAYV,OAAQ;MACpB,iBAAeO,aAAc;MAC7BiB,SAAS,EAAExC,UAAU,CAAC,eAAe,EAAE;QACnC,iBAAiB,EAAEuB,aAAa;QAChC,iBAAiB,EAAEX;MACvB,CAAC,CAAE;MACH,qBAAmBJ,eAAgB;MACnC,eAAaC,UAAW;MACxBF,GAAG,EAAEA,GAAI;MACTmB,EAAE,EAAEA,EAAG;MACPe,QAAQ,EAAE7B,UAAW;MACrBE,OAAO,EAAEa,oBAAqB;MAC9Be,IAAI,EAAC,KAAK;MACVC,QAAQ,EAAEpB,aAAa,GAAG,GAAG,GAAG,IAAK;MACrCqB,IAAI,EAAC,QAAQ;MACbC,OAAO,EAAC;IAAU,GAEjBnC,QACG,CACH,CAAC;EAElB,CACG,CAAC;AAEhB,CAAC,CAAC;AAEF,eAAeN,gBAAgB","ignoreList":[]}
1
+ {"version":3,"file":"SidebarNavButton.js","names":["React","Route","noop","classNames","Button","Tooltip","isLeftClick","SidebarNavButton","forwardRef","props","ref","dataResinTarget","dataTestId","children","elementId","internalSidebarNavigation","internalSidebarNavigationHandler","isDisabled","isOpen","onClick","routerDisabled","sidebarView","tooltip","sidebarPath","id","isMatch","sidebar","isActiveValue","hasExtraParams","versionId","activeFeedEntryType","activeFeedEntryId","fileVersionId","isExactMatch","handleNavButtonClick","event","defaultPrevented","replace","open","createElement","position","text","isTabbable","accessibleWhenDisabled","className","disabled","role","tabIndex","type","variant","path","match","history","isExact","method","push","pathname","state"],"sources":["../../../src/elements/content-sidebar/SidebarNavButton.js"],"sourcesContent":["/**\n * @flow\n * @file Preview sidebar nav button component\n * @author Box\n */\n\nimport * as React from 'react';\nimport { Route } from 'react-router-dom';\nimport noop from 'lodash/noop';\nimport classNames from 'classnames';\nimport { Button } from '@box/blueprint-web';\nimport Tooltip from '../../components/tooltip/Tooltip';\nimport { isLeftClick } from '../../utils/dom';\nimport type { InternalSidebarNavigation, InternalSidebarNavigationHandler, ViewTypeValues } from '../common/types/SidebarNavigation';\nimport './SidebarNavButton.scss';\n\ntype Props = {\n 'data-resin-target'?: string,\n 'data-testid'?: string,\n children: React.Node,\n elementId?: string,\n internalSidebarNavigation?: InternalSidebarNavigation,\n internalSidebarNavigationHandler?: InternalSidebarNavigationHandler,\n isDisabled?: boolean,\n isOpen?: boolean,\n onClick?: (sidebarView: ViewTypeValues) => void,\n routerDisabled?: boolean,\n sidebarView: ViewTypeValues,\n tooltip: React.Node,\n};\n\nconst SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props, ref: React.Ref<any>) => {\n const {\n 'data-resin-target': dataResinTarget,\n 'data-testid': dataTestId,\n children,\n elementId = '',\n internalSidebarNavigation,\n internalSidebarNavigationHandler,\n isDisabled,\n isOpen,\n onClick = noop,\n routerDisabled = false,\n sidebarView,\n tooltip,\n } = props;\n const sidebarPath = `/${sidebarView}`;\n const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;\n\n if (routerDisabled) {\n // Mimic router behavior using internalSidebarNavigation\n const isMatch = !!internalSidebarNavigation && internalSidebarNavigation.sidebar === sidebarView;\n const isActiveValue = isMatch && !!isOpen;\n \n // Mimic isExactMatch: true when no extra navigation parameters are present\n const hasExtraParams = internalSidebarNavigation && (\n internalSidebarNavigation.versionId ||\n internalSidebarNavigation.activeFeedEntryType ||\n internalSidebarNavigation.activeFeedEntryId ||\n internalSidebarNavigation.fileVersionId\n );\n const isExactMatch = isMatch && !hasExtraParams;\n \n const handleNavButtonClick = event => {\n onClick(sidebarView);\n \n // Mimic router navigation behavior\n if (internalSidebarNavigationHandler && !event.defaultPrevented && isLeftClick(event)) {\n const replace = isExactMatch;\n internalSidebarNavigationHandler({\n sidebar: sidebarView,\n open: true,\n }, replace);\n }\n };\n\n return (\n <Tooltip position=\"middle-left\" text={tooltip} isTabbable={false}>\n <Button\n accessibleWhenDisabled={true}\n aria-controls={`${id}-content`}\n aria-label={tooltip}\n aria-selected={isActiveValue}\n className={classNames('bcs-NavButton', {\n 'bcs-is-selected': isActiveValue,\n 'bdl-is-disabled': isDisabled,\n })}\n data-resin-target={dataResinTarget}\n data-testid={dataTestId}\n ref={ref}\n id={id}\n disabled={isDisabled}\n onClick={handleNavButtonClick}\n role=\"tab\"\n tabIndex={isActiveValue ? '0' : '-1'}\n type=\"button\"\n variant=\"tertiary\"\n >\n {children}\n </Button>\n </Tooltip>\n );\n }\n\n return (\n <Route path={sidebarPath}>\n {({ match, history }) => {\n const isMatch = !!match;\n const isActiveValue = isMatch && !!isOpen;\n const isExactMatch = isMatch && match.isExact;\n\n const handleNavButtonClick = event => {\n onClick(sidebarView);\n\n if (!event.defaultPrevented && isLeftClick(event)) {\n const method = isExactMatch ? history.replace : history.push;\n method({\n pathname: sidebarPath,\n state: { open: true },\n });\n }\n };\n\n return (\n <Tooltip position=\"middle-left\" text={tooltip} isTabbable={false}>\n <Button\n accessibleWhenDisabled={true}\n aria-controls={`${id}-content`}\n aria-label={tooltip}\n aria-selected={isActiveValue}\n className={classNames('bcs-NavButton', {\n 'bcs-is-selected': isActiveValue,\n 'bdl-is-disabled': isDisabled,\n })}\n data-resin-target={dataResinTarget}\n data-testid={dataTestId}\n ref={ref}\n id={id}\n disabled={isDisabled}\n onClick={handleNavButtonClick}\n role=\"tab\"\n tabIndex={isActiveValue ? '0' : '-1'}\n type=\"button\"\n variant=\"tertiary\"\n >\n {children}\n </Button>\n </Tooltip>\n );\n }}\n </Route>\n );\n});\n\nexport default SidebarNavButton;\n"],"mappings":"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","ignoreList":[]}
@@ -12,6 +12,13 @@
12
12
  .header-flyout-overlay {
13
13
  width: $flyout-list-width;
14
14
  height: $flyout-list-height;
15
+ animation: bdl-open-flyout-animation 0.3s cubic-bezier(0.32, 0, 0.33, 1.3);
16
+ animation-fill-mode: both;
17
+ transform-origin: top right;
18
+
19
+ @media (prefers-reduced-motion: reduce) {
20
+ animation: none;
21
+ }
15
22
 
16
23
  .overlay {
17
24
  @include bdl-Overlay-container;
@@ -27,6 +34,18 @@
27
34
  }
28
35
  }
29
36
 
37
+ @keyframes bdl-open-flyout-animation {
38
+ from {
39
+ opacity: 0;
40
+ transform: translateY(-10px) scale(0.95);
41
+ }
42
+
43
+ to {
44
+ opacity: 1;
45
+ transform: translateY(0) scale(1);
46
+ }
47
+ }
48
+
30
49
  .header-flyout-list-container {
31
50
  .flyout-list-container-body {
32
51
  // the overall height, minus the top and bottom padding
@@ -102,6 +121,8 @@
102
121
  }
103
122
 
104
123
  .header-flyout-overlay {
124
+ animation: none;
125
+
105
126
  .header-flyout-title {
106
127
  padding-bottom: 0;
107
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-ui-elements",
3
- "version": "23.4.0-beta.20",
3
+ "version": "23.4.0-beta.22",
4
4
  "description": "Box UI Elements",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -11,6 +11,7 @@ import classNames from 'classnames';
11
11
  import { Button } from '@box/blueprint-web';
12
12
  import Tooltip from '../../components/tooltip/Tooltip';
13
13
  import { isLeftClick } from '../../utils/dom';
14
+ import type { InternalSidebarNavigation, InternalSidebarNavigationHandler, ViewTypeValues } from '../common/types/SidebarNavigation';
14
15
  import './SidebarNavButton.scss';
15
16
 
16
17
  type Props = {
@@ -18,10 +19,13 @@ type Props = {
18
19
  'data-testid'?: string,
19
20
  children: React.Node,
20
21
  elementId?: string,
22
+ internalSidebarNavigation?: InternalSidebarNavigation,
23
+ internalSidebarNavigationHandler?: InternalSidebarNavigationHandler,
21
24
  isDisabled?: boolean,
22
25
  isOpen?: boolean,
23
- onClick?: (sidebarView: string) => void,
24
- sidebarView: string,
26
+ onClick?: (sidebarView: ViewTypeValues) => void,
27
+ routerDisabled?: boolean,
28
+ sidebarView: ViewTypeValues,
25
29
  tooltip: React.Node,
26
30
  };
27
31
 
@@ -31,13 +35,72 @@ const SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props,
31
35
  'data-testid': dataTestId,
32
36
  children,
33
37
  elementId = '',
38
+ internalSidebarNavigation,
39
+ internalSidebarNavigationHandler,
34
40
  isDisabled,
35
41
  isOpen,
36
42
  onClick = noop,
43
+ routerDisabled = false,
37
44
  sidebarView,
38
45
  tooltip,
39
46
  } = props;
40
47
  const sidebarPath = `/${sidebarView}`;
48
+ const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
49
+
50
+ if (routerDisabled) {
51
+ // Mimic router behavior using internalSidebarNavigation
52
+ const isMatch = !!internalSidebarNavigation && internalSidebarNavigation.sidebar === sidebarView;
53
+ const isActiveValue = isMatch && !!isOpen;
54
+
55
+ // Mimic isExactMatch: true when no extra navigation parameters are present
56
+ const hasExtraParams = internalSidebarNavigation && (
57
+ internalSidebarNavigation.versionId ||
58
+ internalSidebarNavigation.activeFeedEntryType ||
59
+ internalSidebarNavigation.activeFeedEntryId ||
60
+ internalSidebarNavigation.fileVersionId
61
+ );
62
+ const isExactMatch = isMatch && !hasExtraParams;
63
+
64
+ const handleNavButtonClick = event => {
65
+ onClick(sidebarView);
66
+
67
+ // Mimic router navigation behavior
68
+ if (internalSidebarNavigationHandler && !event.defaultPrevented && isLeftClick(event)) {
69
+ const replace = isExactMatch;
70
+ internalSidebarNavigationHandler({
71
+ sidebar: sidebarView,
72
+ open: true,
73
+ }, replace);
74
+ }
75
+ };
76
+
77
+ return (
78
+ <Tooltip position="middle-left" text={tooltip} isTabbable={false}>
79
+ <Button
80
+ accessibleWhenDisabled={true}
81
+ aria-controls={`${id}-content`}
82
+ aria-label={tooltip}
83
+ aria-selected={isActiveValue}
84
+ className={classNames('bcs-NavButton', {
85
+ 'bcs-is-selected': isActiveValue,
86
+ 'bdl-is-disabled': isDisabled,
87
+ })}
88
+ data-resin-target={dataResinTarget}
89
+ data-testid={dataTestId}
90
+ ref={ref}
91
+ id={id}
92
+ disabled={isDisabled}
93
+ onClick={handleNavButtonClick}
94
+ role="tab"
95
+ tabIndex={isActiveValue ? '0' : '-1'}
96
+ type="button"
97
+ variant="tertiary"
98
+ >
99
+ {children}
100
+ </Button>
101
+ </Tooltip>
102
+ );
103
+ }
41
104
 
42
105
  return (
43
106
  <Route path={sidebarPath}>
@@ -45,7 +108,6 @@ const SidebarNavButton = React.forwardRef<Props, React.Ref<any>>((props: Props,
45
108
  const isMatch = !!match;
46
109
  const isActiveValue = isMatch && !!isOpen;
47
110
  const isExactMatch = isMatch && match.isExact;
48
- const id = `${elementId}${elementId === '' ? '' : '_'}${sidebarView}`;
49
111
 
50
112
  const handleNavButtonClick = event => {
51
113
  onClick(sidebarView);
@@ -1,5 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { MemoryRouter, Router } from 'react-router-dom';
3
+ // Using fireEvent for all click interactions instead of userEvent because
4
+ // userEvent.pointer with right-click doesn't reliably trigger onClick handlers
3
5
  import { render, screen, fireEvent } from '../../../test-utils/testing-library';
4
6
  import SidebarNavButton from '../SidebarNavButton';
5
7
 
@@ -197,7 +199,7 @@ describe('elements/content-sidebar/SidebarNavButton', () => {
197
199
  renderWithRouter({ onClick: mockOnClick }, mockHistoryWithDifferentPath);
198
200
 
199
201
  const button = screen.getByText('Activity');
200
- fireEvent.click(button, { button: 0 });
202
+ fireEvent.click(button);
201
203
 
202
204
  expect(mockOnClick).toBeCalledWith('activity');
203
205
  expect(mockHistoryPush).toBeCalledWith({
@@ -213,7 +215,7 @@ describe('elements/content-sidebar/SidebarNavButton', () => {
213
215
  renderWithRouter({ onClick: mockOnClick });
214
216
 
215
217
  const button = screen.getByText('Activity');
216
- fireEvent.click(button, { button: 0 });
218
+ fireEvent.click(button);
217
219
 
218
220
  expect(mockOnClick).toBeCalledWith('activity');
219
221
  expect(mockHistoryReplace).toBeCalledWith({
@@ -243,7 +245,6 @@ describe('elements/content-sidebar/SidebarNavButton', () => {
243
245
 
244
246
  const button = screen.getByText('Activity');
245
247
 
246
- // Prevent default on the button click
247
248
  button.addEventListener('click', e => e.preventDefault());
248
249
  fireEvent.click(button, { button: 0 });
249
250
 
@@ -253,3 +254,154 @@ describe('elements/content-sidebar/SidebarNavButton', () => {
253
254
  });
254
255
  });
255
256
  });
257
+
258
+ describe('elements/content-sidebar/SidebarNavButton - Router Disabled', () => {
259
+ beforeEach(() => {
260
+ jest.clearAllMocks();
261
+ });
262
+
263
+ const defaultProps = {
264
+ routerDisabled: true,
265
+ tooltip: 'foo',
266
+ sidebarView: 'activity',
267
+ internalSidebarNavigation: { sidebar: 'skills' },
268
+ };
269
+
270
+ const renderWithoutRouter = ({ children = 'test button', ref, ...props }) =>
271
+ render(
272
+ <SidebarNavButton ref={ref} {...defaultProps} {...props}>
273
+ {children}
274
+ </SidebarNavButton>,
275
+ );
276
+
277
+ test('should render nav button properly', () => {
278
+ renderWithoutRouter({});
279
+ const button = screen.getByRole('tab');
280
+
281
+ expect(button).toHaveAttribute('aria-label', 'foo');
282
+ expect(button).toHaveAttribute('aria-selected', 'false');
283
+ expect(button).toHaveAttribute('aria-controls', 'activity-content');
284
+ expect(button).toHaveAttribute('role', 'tab');
285
+ expect(button).toHaveAttribute('tabindex', '-1');
286
+ expect(button).toHaveAttribute('type', 'button');
287
+ expect(button).toHaveAttribute('id', 'activity');
288
+ expect(button).toHaveClass('bcs-NavButton');
289
+ expect(button).not.toHaveClass('bcs-is-selected');
290
+ expect(button).toHaveTextContent('test button');
291
+ });
292
+
293
+ test.each`
294
+ internalSidebarNavigation | expected
295
+ ${null} | ${false}
296
+ ${undefined} | ${false}
297
+ ${{ sidebar: 'skills' }} | ${false}
298
+ ${{ sidebar: 'activity' }} | ${true}
299
+ ${{ sidebar: 'activity', versionId: '123' }} | ${true}
300
+ `('should reflect active state ($expected) correctly based on internal navigation', ({ expected, internalSidebarNavigation }) => {
301
+ renderWithoutRouter({
302
+ internalSidebarNavigation,
303
+ isOpen: true,
304
+ });
305
+ const button = screen.getByRole('tab');
306
+
307
+ if (expected) {
308
+ expect(button).toHaveClass('bcs-is-selected');
309
+ expect(button).toHaveAttribute('aria-selected', 'true');
310
+ expect(button).toHaveAttribute('tabindex', '0');
311
+ } else {
312
+ expect(button).not.toHaveClass('bcs-is-selected');
313
+ expect(button).toHaveAttribute('aria-selected', 'false');
314
+ expect(button).toHaveAttribute('tabindex', '-1');
315
+ }
316
+ });
317
+
318
+ test('should call onClick with sidebarView when clicked', () => {
319
+ const mockOnClick = jest.fn();
320
+ const mockSidebarView = 'activity';
321
+
322
+ renderWithoutRouter({
323
+ onClick: mockOnClick,
324
+ sidebarView: mockSidebarView,
325
+ });
326
+ const button = screen.getByRole('tab');
327
+
328
+ fireEvent.click(button);
329
+ expect(mockOnClick).toBeCalledWith(mockSidebarView);
330
+ });
331
+
332
+ describe('navigation on click', () => {
333
+ const mockInternalSidebarNavigationHandler = jest.fn();
334
+
335
+ test('calls onClick handler and internalSidebarNavigationHandler with replace=false when not exact match', () => {
336
+ const mockOnClick = jest.fn();
337
+
338
+ renderWithoutRouter({
339
+ onClick: mockOnClick,
340
+ internalSidebarNavigation: { sidebar: 'activity', versionId: '123' },
341
+ internalSidebarNavigationHandler: mockInternalSidebarNavigationHandler,
342
+ });
343
+
344
+ const button = screen.getByRole('tab');
345
+ fireEvent.click(button);
346
+
347
+ expect(mockOnClick).toBeCalledWith('activity');
348
+ expect(mockInternalSidebarNavigationHandler).toBeCalledWith({
349
+ sidebar: 'activity',
350
+ open: true,
351
+ }, false);
352
+ });
353
+
354
+ test('calls internalSidebarNavigationHandler with replace=true when exact match', () => {
355
+ const mockOnClick = jest.fn();
356
+
357
+ renderWithoutRouter({
358
+ onClick: mockOnClick,
359
+ internalSidebarNavigation: { sidebar: 'activity' },
360
+ internalSidebarNavigationHandler: mockInternalSidebarNavigationHandler,
361
+ });
362
+
363
+ const button = screen.getByRole('tab');
364
+ fireEvent.click(button);
365
+
366
+ expect(mockOnClick).toBeCalledWith('activity');
367
+ expect(mockInternalSidebarNavigationHandler).toBeCalledWith({
368
+ sidebar: 'activity',
369
+ open: true,
370
+ }, true);
371
+ });
372
+
373
+ test('does not call internalSidebarNavigationHandler on right click', () => {
374
+ const mockOnClick = jest.fn();
375
+
376
+ renderWithoutRouter({
377
+ onClick: mockOnClick,
378
+ internalSidebarNavigation: { sidebar: 'activity' },
379
+ internalSidebarNavigationHandler: mockInternalSidebarNavigationHandler,
380
+ });
381
+
382
+ const button = screen.getByRole('tab');
383
+ fireEvent.click(button, { button: 1 });
384
+
385
+ expect(mockOnClick).toBeCalledWith('activity');
386
+ expect(mockInternalSidebarNavigationHandler).not.toBeCalled();
387
+ });
388
+
389
+ test('does not call internalSidebarNavigationHandler on prevented event', () => {
390
+ const mockOnClick = jest.fn();
391
+
392
+ renderWithoutRouter({
393
+ onClick: mockOnClick,
394
+ internalSidebarNavigation: { sidebar: 'activity' },
395
+ internalSidebarNavigationHandler: mockInternalSidebarNavigationHandler,
396
+ });
397
+
398
+ const button = screen.getByRole('tab');
399
+
400
+ button.addEventListener('click', e => e.preventDefault());
401
+ fireEvent.click(button, { button: 0 });
402
+
403
+ expect(mockOnClick).toBeCalledWith('activity');
404
+ expect(mockInternalSidebarNavigationHandler).not.toBeCalled();
405
+ });
406
+ });
407
+ });
@@ -12,6 +12,13 @@
12
12
  .header-flyout-overlay {
13
13
  width: $flyout-list-width;
14
14
  height: $flyout-list-height;
15
+ animation: bdl-open-flyout-animation 0.3s cubic-bezier(0.32, 0, 0.33, 1.3);
16
+ animation-fill-mode: both;
17
+ transform-origin: top right;
18
+
19
+ @media (prefers-reduced-motion: reduce) {
20
+ animation: none;
21
+ }
15
22
 
16
23
  .overlay {
17
24
  @include bdl-Overlay-container;
@@ -27,6 +34,18 @@
27
34
  }
28
35
  }
29
36
 
37
+ @keyframes bdl-open-flyout-animation {
38
+ from {
39
+ opacity: 0;
40
+ transform: translateY(-10px) scale(0.95);
41
+ }
42
+
43
+ to {
44
+ opacity: 1;
45
+ transform: translateY(0) scale(1);
46
+ }
47
+ }
48
+
30
49
  .header-flyout-list-container {
31
50
  .flyout-list-container-body {
32
51
  // the overall height, minus the top and bottom padding
@@ -102,6 +121,8 @@
102
121
  }
103
122
 
104
123
  .header-flyout-overlay {
124
+ animation: none;
125
+
105
126
  .header-flyout-title {
106
127
  padding-bottom: 0;
107
128
  }