@planningcenter/topbar 11.1.2-rc.2 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/topbar.js +101 -1
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +101 -1
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +101 -1
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +101 -1
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/topbar.js
CHANGED
|
@@ -1925,6 +1925,9 @@ const AppsButton = ({
|
|
|
1925
1925
|
...nativeProps
|
|
1926
1926
|
}) => {
|
|
1927
1927
|
return /*#__PURE__*/React.createElement(Unbutton, {
|
|
1928
|
+
"aria-expanded": expanded,
|
|
1929
|
+
"aria-haspopup": "true",
|
|
1930
|
+
"aria-label": `App switcher: ${appName}`,
|
|
1928
1931
|
onClick: onClick,
|
|
1929
1932
|
className: classnames(styles$9.button, {
|
|
1930
1933
|
[styles$9["is-expanded"]]: expanded
|
|
@@ -1959,6 +1962,72 @@ function getAppsSwitchProductPath(productName) {
|
|
|
1959
1962
|
return `${basePath}?${params.toString()}`;
|
|
1960
1963
|
}
|
|
1961
1964
|
|
|
1965
|
+
// Currently scoped to links and buttons since the app switcher only contains
|
|
1966
|
+
// those elements. If this hook is reused in containers with form elements,
|
|
1967
|
+
// expand to include: input, select, textarea, [tabindex]:not([tabindex="-1"])
|
|
1968
|
+
const FOCUSABLE_SELECTOR = "a[href], button:not([disabled])";
|
|
1969
|
+
/**
|
|
1970
|
+
* Manages focus trapping within a container element.
|
|
1971
|
+
*
|
|
1972
|
+
* When `active` becomes true:
|
|
1973
|
+
* - Stores the currently focused element
|
|
1974
|
+
* - Moves focus to the first focusable child
|
|
1975
|
+
*
|
|
1976
|
+
* When `active` becomes false:
|
|
1977
|
+
* - Returns focus to the previously focused element
|
|
1978
|
+
*
|
|
1979
|
+
* While active:
|
|
1980
|
+
* - Tab/Shift+Tab wraps within the container
|
|
1981
|
+
* - Escape calls `onClose`
|
|
1982
|
+
*
|
|
1983
|
+
* Note: focus trapping uses scoped querySelector on the container ref,
|
|
1984
|
+
* which is the same approach used by libraries like focus-trap-react.
|
|
1985
|
+
*/
|
|
1986
|
+
function useFocusTrap({
|
|
1987
|
+
active,
|
|
1988
|
+
onClose
|
|
1989
|
+
}) {
|
|
1990
|
+
const containerRef = React.useRef(null);
|
|
1991
|
+
const previousFocusRef = React.useRef(null);
|
|
1992
|
+
React.useLayoutEffect(() => {
|
|
1993
|
+
var _a, _b, _c;
|
|
1994
|
+
if (active) {
|
|
1995
|
+
previousFocusRef.current = document.activeElement;
|
|
1996
|
+
(_b = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(FOCUSABLE_SELECTOR)) === null || _b === void 0 ? void 0 : _b.focus();
|
|
1997
|
+
} else {
|
|
1998
|
+
(_c = previousFocusRef.current) === null || _c === void 0 ? void 0 : _c.focus();
|
|
1999
|
+
previousFocusRef.current = null;
|
|
2000
|
+
}
|
|
2001
|
+
}, [active]);
|
|
2002
|
+
const handleKeyDown = React.useCallback(e => {
|
|
2003
|
+
var _a;
|
|
2004
|
+
if (!active) {
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (e.key === "Escape") {
|
|
2008
|
+
onClose();
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (e.key === "Tab") {
|
|
2012
|
+
const focusable = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
2013
|
+
if (!focusable || focusable.length === 0) return;
|
|
2014
|
+
const first = focusable[0];
|
|
2015
|
+
const last = focusable[focusable.length - 1];
|
|
2016
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
2017
|
+
e.preventDefault();
|
|
2018
|
+
last.focus();
|
|
2019
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
2020
|
+
e.preventDefault();
|
|
2021
|
+
first.focus();
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}, [active, onClose]);
|
|
2025
|
+
return {
|
|
2026
|
+
containerRef,
|
|
2027
|
+
handleKeyDown
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1962
2031
|
const fontFamily = {
|
|
1963
2032
|
fontFamily: `inherit`
|
|
1964
2033
|
};
|
|
@@ -2060,6 +2129,13 @@ const AppsMenu = ({
|
|
|
2060
2129
|
const restProducts = [...apps].filter(({
|
|
2061
2130
|
name
|
|
2062
2131
|
}) => name !== "Home" && name !== "Accounts").sort((a, b) => a.name.localeCompare(b.name));
|
|
2132
|
+
const {
|
|
2133
|
+
containerRef: menuRef,
|
|
2134
|
+
handleKeyDown
|
|
2135
|
+
} = useFocusTrap({
|
|
2136
|
+
active: visible,
|
|
2137
|
+
onClose: toggle
|
|
2138
|
+
});
|
|
2063
2139
|
const listItemProps = {
|
|
2064
2140
|
...fontFamily,
|
|
2065
2141
|
alignItems: "center",
|
|
@@ -2076,12 +2152,16 @@ const AppsMenu = ({
|
|
|
2076
2152
|
className: styles$8.popup,
|
|
2077
2153
|
isVisible: visible
|
|
2078
2154
|
}, /*#__PURE__*/React.createElement("menu", {
|
|
2155
|
+
ref: menuRef,
|
|
2079
2156
|
"data-pendo": "app-switcher",
|
|
2157
|
+
onKeyDown: handleKeyDown,
|
|
2080
2158
|
style: {
|
|
2081
2159
|
margin: 0,
|
|
2082
2160
|
padding: 0
|
|
2083
2161
|
}
|
|
2084
2162
|
}, homeProduct && (/*#__PURE__*/React.createElement(HoverableListItem, {
|
|
2163
|
+
"aria-current": appName === homeProduct.name ? "page" : undefined,
|
|
2164
|
+
"aria-label": homeProduct.name,
|
|
2085
2165
|
component: "a",
|
|
2086
2166
|
"data-pendo": `app-switcher--change-app--${homeProduct.name}`,
|
|
2087
2167
|
"data-turbolinks": false,
|
|
@@ -2106,6 +2186,8 @@ const AppsMenu = ({
|
|
|
2106
2186
|
const isFirst = i === 0;
|
|
2107
2187
|
const isLast = i === restProducts.length - 1;
|
|
2108
2188
|
return /*#__PURE__*/React.createElement(HoverableListItem, {
|
|
2189
|
+
"aria-current": name === appName ? "page" : undefined,
|
|
2190
|
+
"aria-label": name,
|
|
2109
2191
|
component: "a",
|
|
2110
2192
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
2111
2193
|
"data-turbolinks": false,
|
|
@@ -2129,6 +2211,8 @@ const AppsMenu = ({
|
|
|
2129
2211
|
size: 18
|
|
2130
2212
|
}));
|
|
2131
2213
|
}), accountsProduct && (/*#__PURE__*/React.createElement(HoverableListItem, {
|
|
2214
|
+
"aria-current": appName === accountsProduct.name ? "page" : undefined,
|
|
2215
|
+
"aria-label": accountsProduct.name,
|
|
2132
2216
|
component: "a",
|
|
2133
2217
|
"data-pendo": `app-switcher--change-app--${accountsProduct.name}`,
|
|
2134
2218
|
"data-turbolinks": false,
|
|
@@ -2628,6 +2712,13 @@ const SmallTopbar = ({
|
|
|
2628
2712
|
const restProducts = [...props.apps].filter(({
|
|
2629
2713
|
name
|
|
2630
2714
|
}) => name !== "Home" && name !== "Accounts");
|
|
2715
|
+
const {
|
|
2716
|
+
containerRef: appMenuRef,
|
|
2717
|
+
handleKeyDown: handleAppMenuKeyDown
|
|
2718
|
+
} = useFocusTrap({
|
|
2719
|
+
active: productSwitcherVisible,
|
|
2720
|
+
onClose: () => setProductSwitcherVisible(false)
|
|
2721
|
+
});
|
|
2631
2722
|
React.useEffect(() => {
|
|
2632
2723
|
setRoutesMenuVisible(false);
|
|
2633
2724
|
}, [activeRoute && ((_b = (_a = activeRoute[0]) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.href)]);
|
|
@@ -2636,6 +2727,9 @@ const SmallTopbar = ({
|
|
|
2636
2727
|
...props.style
|
|
2637
2728
|
}
|
|
2638
2729
|
}, /*#__PURE__*/React.createElement("button", {
|
|
2730
|
+
"aria-expanded": productSwitcherVisible,
|
|
2731
|
+
"aria-haspopup": "true",
|
|
2732
|
+
"aria-label": `App switcher: ${props.appName}`,
|
|
2639
2733
|
type: "button",
|
|
2640
2734
|
className: styles$3["appmenu-button"],
|
|
2641
2735
|
onClick: () => {
|
|
@@ -2651,6 +2745,7 @@ const SmallTopbar = ({
|
|
|
2651
2745
|
}, props.routes)), /*#__PURE__*/React.createElement(ClientStorage, {
|
|
2652
2746
|
item: "Topbar:Small:Menus:Products:visibility",
|
|
2653
2747
|
render: () => (/*#__PURE__*/React.createElement("div", {
|
|
2748
|
+
ref: appMenuRef,
|
|
2654
2749
|
className: styles$3["appmenu-layer"],
|
|
2655
2750
|
style: {
|
|
2656
2751
|
...(productSwitcherVisible && {
|
|
@@ -2659,7 +2754,8 @@ const SmallTopbar = ({
|
|
|
2659
2754
|
},
|
|
2660
2755
|
onClick: () => {
|
|
2661
2756
|
setProductSwitcherVisible(visible => !visible);
|
|
2662
|
-
}
|
|
2757
|
+
},
|
|
2758
|
+
onKeyDown: handleAppMenuKeyDown
|
|
2663
2759
|
}, /*#__PURE__*/React.createElement(CloseMenuButton, null), /*#__PURE__*/React.createElement("div", {
|
|
2664
2760
|
onClick: e => {
|
|
2665
2761
|
const {
|
|
@@ -2671,6 +2767,8 @@ const SmallTopbar = ({
|
|
|
2671
2767
|
},
|
|
2672
2768
|
className: styles$3["appmenu-container"]
|
|
2673
2769
|
}, /*#__PURE__*/React.createElement("a", {
|
|
2770
|
+
"aria-current": props.appName === (homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) ? "page" : undefined,
|
|
2771
|
+
"aria-label": homeProduct ? homeProduct.name : props.appName,
|
|
2674
2772
|
"data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
|
|
2675
2773
|
"data-turbolinks": false,
|
|
2676
2774
|
className: classnames(styles$3["appmenu-list-a"], styles$3["appmenu-list-a--top"]),
|
|
@@ -2688,6 +2786,8 @@ const SmallTopbar = ({
|
|
|
2688
2786
|
key: name,
|
|
2689
2787
|
className: styles$3["appmenu-list-li"]
|
|
2690
2788
|
}, /*#__PURE__*/React.createElement("a", {
|
|
2789
|
+
"aria-current": name === props.appName ? "page" : undefined,
|
|
2790
|
+
"aria-label": name,
|
|
2691
2791
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
2692
2792
|
"data-turbolinks": false,
|
|
2693
2793
|
className: styles$3["appmenu-list-a"],
|