@capra/core 1.5.0 → 1.5.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/README.md +6 -0
- package/dist/index.cjs +326 -109
- package/dist/index.d.cts +8 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.mjs +326 -109
- package/dist/style.css +93 -67
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
`@capra/core` publishes the foundational React components for the Capra design system, along with the package stylesheet entry point at `@capra/core/styles.css`.
|
|
6
6
|
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
Human-readable documentation is available at https://capra.cribl.io.
|
|
10
|
+
|
|
11
|
+
LLM-readable documentation is available at https://capra.cribl.io/llms.txt.
|
|
12
|
+
|
|
7
13
|
## Install
|
|
8
14
|
|
|
9
15
|
```sh
|
package/dist/index.cjs
CHANGED
|
@@ -2937,20 +2937,74 @@ function createConfirmationModalOpener(appearance) {
|
|
|
2937
2937
|
//#endregion
|
|
2938
2938
|
//#region src/components/Drawer/Drawer.module.css
|
|
2939
2939
|
var Drawer_module_default = {
|
|
2940
|
-
"body": "capra-Drawer-module-
|
|
2941
|
-
"description": "capra-Drawer-module-
|
|
2942
|
-
"drawerInFromLeft": "capra-Drawer-module-
|
|
2943
|
-
"drawerInFromRight": "capra-Drawer-module-
|
|
2944
|
-
"
|
|
2945
|
-
"
|
|
2946
|
-
"
|
|
2947
|
-
"
|
|
2948
|
-
"
|
|
2949
|
-
"
|
|
2950
|
-
"
|
|
2951
|
-
"
|
|
2940
|
+
"body": "capra-Drawer-module-H0eofa-body",
|
|
2941
|
+
"description": "capra-Drawer-module-H0eofa-description",
|
|
2942
|
+
"drawerInFromLeft": "capra-Drawer-module-H0eofa-drawerInFromLeft",
|
|
2943
|
+
"drawerInFromRight": "capra-Drawer-module-H0eofa-drawerInFromRight",
|
|
2944
|
+
"drawerOutToLeft": "capra-Drawer-module-H0eofa-drawerOutToLeft",
|
|
2945
|
+
"drawerOutToRight": "capra-Drawer-module-H0eofa-drawerOutToRight",
|
|
2946
|
+
"expandedTitleLayout": "capra-Drawer-module-H0eofa-expandedTitleLayout",
|
|
2947
|
+
"footer": "capra-Drawer-module-H0eofa-footer",
|
|
2948
|
+
"header": "capra-Drawer-module-H0eofa-header",
|
|
2949
|
+
"headerMain": "capra-Drawer-module-H0eofa-headerMain",
|
|
2950
|
+
"headerTrailing": "capra-Drawer-module-H0eofa-headerTrailing",
|
|
2951
|
+
"overlay": "capra-Drawer-module-H0eofa-overlay",
|
|
2952
|
+
"root": "capra-Drawer-module-H0eofa-root",
|
|
2953
|
+
"sheet": "capra-Drawer-module-H0eofa-sheet"
|
|
2952
2954
|
};
|
|
2953
2955
|
//#endregion
|
|
2956
|
+
//#region src/components/Drawer/useDrawerExitAnimation.ts
|
|
2957
|
+
/** Matches `motion.duration.medium.2` — keep in sync with Drawer.module.css sheet animations. */
|
|
2958
|
+
const drawerAnimationMs = 300;
|
|
2959
|
+
/**
|
|
2960
|
+
* Keeps the drawer portal mounted through the close animation and unmounts after it finishes
|
|
2961
|
+
* (via `animationend` or a duration fallback timeout).
|
|
2962
|
+
*/
|
|
2963
|
+
function useDrawerExitAnimation(isOpen) {
|
|
2964
|
+
const [isRendered, setIsRendered] = react$1.useState(isOpen);
|
|
2965
|
+
const closeTimeoutRef = react$1.useRef(void 0);
|
|
2966
|
+
const finishCloseAnimation = react$1.useCallback(() => {
|
|
2967
|
+
if (isOpen) return;
|
|
2968
|
+
if (closeTimeoutRef.current !== void 0) {
|
|
2969
|
+
window.clearTimeout(closeTimeoutRef.current);
|
|
2970
|
+
closeTimeoutRef.current = void 0;
|
|
2971
|
+
}
|
|
2972
|
+
setIsRendered(false);
|
|
2973
|
+
}, [isOpen]);
|
|
2974
|
+
react$1.useEffect(() => {
|
|
2975
|
+
if (isOpen) {
|
|
2976
|
+
if (closeTimeoutRef.current !== void 0) {
|
|
2977
|
+
window.clearTimeout(closeTimeoutRef.current);
|
|
2978
|
+
closeTimeoutRef.current = void 0;
|
|
2979
|
+
}
|
|
2980
|
+
setIsRendered(true);
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
if (!isRendered) return;
|
|
2984
|
+
if (typeof window === "undefined") {
|
|
2985
|
+
setIsRendered(false);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
closeTimeoutRef.current = window.setTimeout(() => {
|
|
2989
|
+
closeTimeoutRef.current = void 0;
|
|
2990
|
+
setIsRendered(false);
|
|
2991
|
+
}, drawerAnimationMs);
|
|
2992
|
+
return () => {
|
|
2993
|
+
if (closeTimeoutRef.current !== void 0) {
|
|
2994
|
+
window.clearTimeout(closeTimeoutRef.current);
|
|
2995
|
+
closeTimeoutRef.current = void 0;
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
}, [isOpen, isRendered]);
|
|
2999
|
+
return {
|
|
3000
|
+
isRendered,
|
|
3001
|
+
onSheetAnimationEnd: react$1.useCallback((event) => {
|
|
3002
|
+
if (event.target !== event.currentTarget || isOpen) return;
|
|
3003
|
+
finishCloseAnimation();
|
|
3004
|
+
}, [finishCloseAnimation, isOpen])
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
//#endregion
|
|
2954
3008
|
//#region src/components/Drawer/Drawer.tsx
|
|
2955
3009
|
const DrawerTitleContext = react.createContext(void 0);
|
|
2956
3010
|
function cssSize(value) {
|
|
@@ -2958,11 +3012,6 @@ function cssSize(value) {
|
|
|
2958
3012
|
return typeof value === "number" ? `${value}px` : value;
|
|
2959
3013
|
}
|
|
2960
3014
|
const defaultWidth = 480;
|
|
2961
|
-
/** History state marker so we can sync `history.back()` when the drawer closes without a popstate. */
|
|
2962
|
-
const DRAWER_HISTORY_STATE = { __capraUiDrawer: 1 };
|
|
2963
|
-
function isDrawerHistoryState(state) {
|
|
2964
|
-
return typeof state === "object" && state !== null && "__capraUiDrawer" in state && state.__capraUiDrawer === DRAWER_HISTORY_STATE.__capraUiDrawer;
|
|
2965
|
-
}
|
|
2966
3015
|
function resolvePortalContainer(getContainer) {
|
|
2967
3016
|
if (typeof document === "undefined") return null;
|
|
2968
3017
|
if (getContainer === false || getContainer === void 0) return document.body;
|
|
@@ -2988,6 +3037,7 @@ function DrawerDescription({ children }) {
|
|
|
2988
3037
|
}
|
|
2989
3038
|
function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right", width, title, footer, closable = true, modal = true, getContainer, FORCE__className, children, "aria-label": ariaLabel }) {
|
|
2990
3039
|
const isModal = modal !== false;
|
|
3040
|
+
const { isRendered, onSheetAnimationEnd } = useDrawerExitAnimation(isOpen);
|
|
2991
3041
|
react.useEffect(() => {
|
|
2992
3042
|
onOpenChange?.(isOpen);
|
|
2993
3043
|
}, [isOpen, onOpenChange]);
|
|
@@ -2999,20 +3049,6 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
|
|
|
2999
3049
|
const requestClose = react.useCallback(() => {
|
|
3000
3050
|
onCloseRef.current?.();
|
|
3001
3051
|
}, []);
|
|
3002
|
-
react.useEffect(() => {
|
|
3003
|
-
if (!isOpen || typeof window === "undefined") return;
|
|
3004
|
-
window.history.pushState(DRAWER_HISTORY_STATE, "", window.location.href);
|
|
3005
|
-
let historyPushed = true;
|
|
3006
|
-
const onPopState = () => {
|
|
3007
|
-
historyPushed = false;
|
|
3008
|
-
requestClose();
|
|
3009
|
-
};
|
|
3010
|
-
window.addEventListener("popstate", onPopState);
|
|
3011
|
-
return () => {
|
|
3012
|
-
window.removeEventListener("popstate", onPopState);
|
|
3013
|
-
if (historyPushed && isDrawerHistoryState(window.history.state)) window.history.back();
|
|
3014
|
-
};
|
|
3015
|
-
}, [isOpen, requestClose]);
|
|
3016
3052
|
react.useEffect(() => {
|
|
3017
3053
|
if (!isOpen) return;
|
|
3018
3054
|
const onKeyDown = (e) => {
|
|
@@ -3035,24 +3071,11 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
|
|
|
3035
3071
|
const sheetVars = react.useMemo(() => ({ "--drawer-width": widthCss }), [widthCss]);
|
|
3036
3072
|
const titleId = react.useId();
|
|
3037
3073
|
const portalContainer = react.useMemo(() => resolvePortalContainer(getContainer), [getContainer]);
|
|
3038
|
-
if (!
|
|
3074
|
+
if (!isRendered) return null;
|
|
3039
3075
|
if (!portalContainer) return null;
|
|
3040
3076
|
const labelledBy = title != null ? titleId : void 0;
|
|
3041
3077
|
const dialogLabel = title != null ? void 0 : ariaLabel ?? "Drawer";
|
|
3042
|
-
|
|
3043
|
-
className: clsx(Drawer_module_default.root, FORCE__className),
|
|
3044
|
-
"data-open": "true",
|
|
3045
|
-
"data-modal": isModal ? "true" : "false"
|
|
3046
|
-
}, isModal ? /* @__PURE__ */ react.createElement("div", {
|
|
3047
|
-
"data-drawer-overlay": true,
|
|
3048
|
-
className: Drawer_module_default.overlay,
|
|
3049
|
-
"aria-hidden": isOpen ? void 0 : true,
|
|
3050
|
-
onPointerDown: onOverlayPointerDown
|
|
3051
|
-
}) : null, /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
|
|
3052
|
-
contain: isOpen && isModal,
|
|
3053
|
-
autoFocus: isOpen && isModal,
|
|
3054
|
-
restoreFocus: true
|
|
3055
|
-
}, /* @__PURE__ */ react.createElement("div", {
|
|
3078
|
+
const sheet = /* @__PURE__ */ react.createElement("div", {
|
|
3056
3079
|
className: Drawer_module_default.sheet,
|
|
3057
3080
|
style: sheetVars,
|
|
3058
3081
|
"data-placement": placement,
|
|
@@ -3061,15 +3084,30 @@ function DrawerInner({ isOpen = false, onClose, onOpenChange, placement = "right
|
|
|
3061
3084
|
"aria-hidden": isOpen ? void 0 : true,
|
|
3062
3085
|
"aria-label": isOpen ? dialogLabel : void 0,
|
|
3063
3086
|
"aria-labelledby": isOpen ? labelledBy : void 0,
|
|
3064
|
-
tabIndex: isOpen && isModal ? -1 : void 0
|
|
3087
|
+
tabIndex: isOpen && isModal ? -1 : void 0,
|
|
3088
|
+
onAnimationEnd: onSheetAnimationEnd
|
|
3065
3089
|
}, (title != null || closable) && /* @__PURE__ */ react.createElement("header", { className: Drawer_module_default.header }, /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.headerMain }, title != null ? /* @__PURE__ */ react.createElement(DrawerTitleContext.Provider, { value: titleId }, typeof title === "string" ? /* @__PURE__ */ react.createElement(DrawerHeading, null, title) : title) : null), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.headerTrailing }, closable ? /* @__PURE__ */ react.createElement(IconButton, {
|
|
3066
3090
|
variant: "tertiary",
|
|
3067
3091
|
appearance: "neutral",
|
|
3068
3092
|
"aria-label": "Close drawer",
|
|
3069
3093
|
size: "sm",
|
|
3070
3094
|
icon: _capra_icons.CloseOutlined,
|
|
3071
|
-
onClick: requestClose
|
|
3072
|
-
}) : null)), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ react.createElement("footer", { className: Drawer_module_default.footer }, footer) : null)
|
|
3095
|
+
onClick: isOpen ? requestClose : void 0
|
|
3096
|
+
}) : null)), /* @__PURE__ */ react.createElement("div", { className: Drawer_module_default.body }, children), footer ? /* @__PURE__ */ react.createElement("footer", { className: Drawer_module_default.footer }, footer) : null);
|
|
3097
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ react.createElement("div", {
|
|
3098
|
+
className: clsx(Drawer_module_default.root, FORCE__className),
|
|
3099
|
+
"data-open": isOpen ? "true" : "false",
|
|
3100
|
+
"data-modal": isModal ? "true" : "false"
|
|
3101
|
+
}, isModal ? /* @__PURE__ */ react.createElement(react.Fragment, null, /* @__PURE__ */ react.createElement("div", {
|
|
3102
|
+
"data-drawer-overlay": true,
|
|
3103
|
+
className: Drawer_module_default.overlay,
|
|
3104
|
+
"aria-hidden": isOpen ? void 0 : true,
|
|
3105
|
+
onPointerDown: isOpen ? onOverlayPointerDown : void 0
|
|
3106
|
+
}), /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
|
|
3107
|
+
contain: true,
|
|
3108
|
+
autoFocus: isOpen,
|
|
3109
|
+
restoreFocus: true
|
|
3110
|
+
}, sheet)) : sheet), portalContainer);
|
|
3073
3111
|
}
|
|
3074
3112
|
/**
|
|
3075
3113
|
* A side sheet aligned with Capra design: supports **modal** (scrim + scroll lock + focus trap) and **non-modal**
|
|
@@ -3389,42 +3427,47 @@ var EmptyState_module_default = {
|
|
|
3389
3427
|
};
|
|
3390
3428
|
//#endregion
|
|
3391
3429
|
//#region src/components/EmptyState/EmptyState.tsx
|
|
3430
|
+
function lazyLoad(importFn) {
|
|
3431
|
+
return react.default.lazy(() => importFn().then((m) => {
|
|
3432
|
+
return { default: m[Object.getOwnPropertyNames(m)[0]] };
|
|
3433
|
+
}));
|
|
3434
|
+
}
|
|
3392
3435
|
const ILLUSTRATIONS = {
|
|
3393
3436
|
EmptyFolder: {
|
|
3394
|
-
light:
|
|
3395
|
-
dark:
|
|
3437
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolder")),
|
|
3438
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyFolderDark"))
|
|
3396
3439
|
},
|
|
3397
3440
|
ArtSupplies: {
|
|
3398
|
-
light:
|
|
3399
|
-
dark:
|
|
3441
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/ArtSupplies")),
|
|
3442
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/ArtSuppliesDark"))
|
|
3400
3443
|
},
|
|
3401
3444
|
Sandcastle: {
|
|
3402
|
-
light:
|
|
3403
|
-
dark:
|
|
3445
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/Sandcastle")),
|
|
3446
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/SandcastleDark"))
|
|
3404
3447
|
},
|
|
3405
3448
|
EmptyBowl: {
|
|
3406
|
-
light:
|
|
3407
|
-
dark:
|
|
3449
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl")),
|
|
3450
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/EmptyBowl"))
|
|
3408
3451
|
},
|
|
3409
3452
|
EmptySuitcase: {
|
|
3410
|
-
light:
|
|
3411
|
-
dark:
|
|
3453
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcase")),
|
|
3454
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/EmptySuitcaseDark"))
|
|
3412
3455
|
},
|
|
3413
3456
|
Envelope: {
|
|
3414
|
-
light:
|
|
3415
|
-
dark:
|
|
3457
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/Envelope")),
|
|
3458
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/Envelope"))
|
|
3416
3459
|
},
|
|
3417
3460
|
MissingSock: {
|
|
3418
|
-
light:
|
|
3419
|
-
dark:
|
|
3461
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/MissingSock")),
|
|
3462
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/MissingSock"))
|
|
3420
3463
|
},
|
|
3421
3464
|
PizzaBox: {
|
|
3422
|
-
light:
|
|
3423
|
-
dark:
|
|
3465
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox")),
|
|
3466
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/PizzaBox"))
|
|
3424
3467
|
},
|
|
3425
3468
|
PottedPlant: {
|
|
3426
|
-
light:
|
|
3427
|
-
dark:
|
|
3469
|
+
light: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant")),
|
|
3470
|
+
dark: lazyLoad(() => import("@capra/icons/esm/images/PottedPlant"))
|
|
3428
3471
|
}
|
|
3429
3472
|
};
|
|
3430
3473
|
/**
|
|
@@ -4295,7 +4338,39 @@ const MenuRootTriggerPressable = react.forwardRef(function MenuRootTriggerPressa
|
|
|
4295
4338
|
}, /* @__PURE__ */ react.createElement(react_aria_components.Pressable, null, children));
|
|
4296
4339
|
});
|
|
4297
4340
|
MenuRootTriggerPressable.displayName = "MenuRootTriggerPressable";
|
|
4298
|
-
|
|
4341
|
+
/**
|
|
4342
|
+
* Wraps the menu list in a focus scope while the overlay is open. Must render under {@link AriaMenuTrigger} so
|
|
4343
|
+
* {@link OverlayTriggerStateContext} resolves for uncontrolled menus.
|
|
4344
|
+
*
|
|
4345
|
+
* `contain` is enabled only after focus moves inside the panel so the trigger (outside this subtree) can still
|
|
4346
|
+
* receive Tab/Enter to open the menu and so programmatic focus from the trigger into the first item is not blocked.
|
|
4347
|
+
*/
|
|
4348
|
+
function MenuPanelFocusScope({ controlledOpen, children }) {
|
|
4349
|
+
const overlayState = react.useContext(react_aria_components.OverlayTriggerStateContext);
|
|
4350
|
+
const isOpen = controlledOpen !== void 0 ? controlledOpen : overlayState?.isOpen ?? false;
|
|
4351
|
+
const rootRef = react.useRef(null);
|
|
4352
|
+
const [focusInsidePanel, setFocusInsidePanel] = react.useState(false);
|
|
4353
|
+
react.useLayoutEffect(() => {
|
|
4354
|
+
if (!isOpen) setFocusInsidePanel(false);
|
|
4355
|
+
}, [isOpen]);
|
|
4356
|
+
const onFocusCapture = react.useCallback(() => {
|
|
4357
|
+
if (isOpen) setFocusInsidePanel(true);
|
|
4358
|
+
}, [isOpen]);
|
|
4359
|
+
const onBlurCapture = react.useCallback((event) => {
|
|
4360
|
+
const next = event.relatedTarget;
|
|
4361
|
+
if (rootRef.current?.contains(next)) return;
|
|
4362
|
+
setFocusInsidePanel(false);
|
|
4363
|
+
}, []);
|
|
4364
|
+
return /* @__PURE__ */ react.createElement("div", {
|
|
4365
|
+
ref: rootRef,
|
|
4366
|
+
onFocusCapture,
|
|
4367
|
+
onBlurCapture
|
|
4368
|
+
}, /* @__PURE__ */ react.createElement(react_aria.FocusScope, {
|
|
4369
|
+
contain: isOpen && focusInsidePanel,
|
|
4370
|
+
restoreFocus: true
|
|
4371
|
+
}, children));
|
|
4372
|
+
}
|
|
4373
|
+
const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAppearance = "default", itemActiveAccentBar = true, itemActiveLabelSemibold = true, panelPadding = "default", popoverOffset = 0, trapFocus = false, FORCE__className: classNameOverride }) => {
|
|
4299
4374
|
const sessionId = react.useId();
|
|
4300
4375
|
const menuPanel = /* @__PURE__ */ react.createElement(react_aria_components.Menu, {
|
|
4301
4376
|
className: clsx(Menu_module_default.menu, classNameOverride),
|
|
@@ -4307,11 +4382,12 @@ const Menu = ({ trigger, children, contentProps, open, onOpenChange, itemHoverAp
|
|
|
4307
4382
|
itemActiveLabelSemibold
|
|
4308
4383
|
})
|
|
4309
4384
|
}, /* @__PURE__ */ react.createElement(MenuSectionIndentContext.Provider, { value: false }, children));
|
|
4385
|
+
const panelBody = trapFocus ? /* @__PURE__ */ react.createElement(MenuPanelFocusScope, { controlledOpen: open }, menuPanel) : menuPanel;
|
|
4310
4386
|
const wrappedPanel = contentProps ? /* @__PURE__ */ react.createElement("div", {
|
|
4311
4387
|
...contentProps,
|
|
4312
4388
|
[MENU_SESSION_DATA_ATTR]: sessionId,
|
|
4313
4389
|
className: contentProps.className
|
|
4314
|
-
},
|
|
4390
|
+
}, panelBody) : /* @__PURE__ */ react.createElement("div", { [MENU_SESSION_DATA_ATTR]: sessionId }, panelBody);
|
|
4315
4391
|
return /* @__PURE__ */ react.createElement(MenuSessionContext.Provider, { value: sessionId }, /* @__PURE__ */ react.createElement(react_aria_components.MenuTrigger, {
|
|
4316
4392
|
isOpen: open,
|
|
4317
4393
|
onOpenChange
|
|
@@ -4337,23 +4413,23 @@ const submenuPanelClassName = Menu_module_default.menu;
|
|
|
4337
4413
|
//#endregion
|
|
4338
4414
|
//#region src/components/TabNav/TabNav.module.css
|
|
4339
4415
|
var TabNav_module_default = {
|
|
4340
|
-
"dropdownIcon": "capra-TabNav-module-
|
|
4341
|
-
"extraContent": "capra-TabNav-module-
|
|
4342
|
-
"indentSpacer": "capra-TabNav-module-
|
|
4343
|
-
"panel": "capra-TabNav-module-
|
|
4344
|
-
"root": "capra-TabNav-module-
|
|
4345
|
-
"subMenu": "capra-TabNav-module-
|
|
4346
|
-
"subMenuItem": "capra-TabNav-module-
|
|
4347
|
-
"subMenuItemActive": "capra-TabNav-module-
|
|
4348
|
-
"subTab": "capra-TabNav-module-
|
|
4349
|
-
"subTabList": "capra-TabNav-module-
|
|
4350
|
-
"subTabListItem": "capra-TabNav-module-
|
|
4351
|
-
"tab": "capra-TabNav-module-
|
|
4352
|
-
"tabBar": "capra-TabNav-module-
|
|
4353
|
-
"tabGroup": "capra-TabNav-module-
|
|
4354
|
-
"tabIcon": "capra-TabNav-module-
|
|
4355
|
-
"tabLabel": "capra-TabNav-module-
|
|
4356
|
-
"tabList": "capra-TabNav-module-
|
|
4416
|
+
"dropdownIcon": "capra-TabNav-module-e0IbmW-dropdownIcon",
|
|
4417
|
+
"extraContent": "capra-TabNav-module-e0IbmW-extraContent",
|
|
4418
|
+
"indentSpacer": "capra-TabNav-module-e0IbmW-indentSpacer",
|
|
4419
|
+
"panel": "capra-TabNav-module-e0IbmW-panel",
|
|
4420
|
+
"root": "capra-TabNav-module-e0IbmW-root",
|
|
4421
|
+
"subMenu": "capra-TabNav-module-e0IbmW-subMenu",
|
|
4422
|
+
"subMenuItem": "capra-TabNav-module-e0IbmW-subMenuItem",
|
|
4423
|
+
"subMenuItemActive": "capra-TabNav-module-e0IbmW-subMenuItemActive",
|
|
4424
|
+
"subTab": "capra-TabNav-module-e0IbmW-subTab",
|
|
4425
|
+
"subTabList": "capra-TabNav-module-e0IbmW-subTabList",
|
|
4426
|
+
"subTabListItem": "capra-TabNav-module-e0IbmW-subTabListItem",
|
|
4427
|
+
"tab": "capra-TabNav-module-e0IbmW-tab",
|
|
4428
|
+
"tabBar": "capra-TabNav-module-e0IbmW-tabBar",
|
|
4429
|
+
"tabGroup": "capra-TabNav-module-e0IbmW-tabGroup",
|
|
4430
|
+
"tabIcon": "capra-TabNav-module-e0IbmW-tabIcon",
|
|
4431
|
+
"tabLabel": "capra-TabNav-module-e0IbmW-tabLabel",
|
|
4432
|
+
"tabList": "capra-TabNav-module-e0IbmW-tabList"
|
|
4357
4433
|
};
|
|
4358
4434
|
//#endregion
|
|
4359
4435
|
//#region src/components/TabNav/TabNav.tsx
|
|
@@ -4373,7 +4449,27 @@ function optionalAriaLabelProps(source) {
|
|
|
4373
4449
|
const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", centered = false, tabBarExtraSlot, wrap = true, FORCE__className: classNameOverride, "aria-label": ariaLabel = "Navigation", ...props }) => {
|
|
4374
4450
|
const [subMenuOpen, setSubMenuOpen] = react.useState({});
|
|
4375
4451
|
const hoverCloseTimeoutRef = react.useRef(null);
|
|
4376
|
-
const
|
|
4452
|
+
const pendingKeyboardFocusKeyRef = react.useRef(null);
|
|
4453
|
+
const menuContentRefs = react.useRef({});
|
|
4454
|
+
const getTriggerElement = react.useCallback((key) => {
|
|
4455
|
+
const trigger = document.getElementById(`tabnav-${key}`);
|
|
4456
|
+
return trigger instanceof HTMLElement ? trigger : null;
|
|
4457
|
+
}, []);
|
|
4458
|
+
const getEnabledMenuItems = react.useCallback((key) => {
|
|
4459
|
+
const menu = menuContentRefs.current[key]?.querySelector("[role=\"menu\"]");
|
|
4460
|
+
if (!(menu instanceof HTMLElement)) return [];
|
|
4461
|
+
return Array.from(menu.querySelectorAll("[role=\"menuitem\"]")).filter((element) => element.getAttribute("aria-disabled") !== "true");
|
|
4462
|
+
}, []);
|
|
4463
|
+
const focusMenuItem = react.useCallback((key, direction) => {
|
|
4464
|
+
const menuItems = getEnabledMenuItems(key);
|
|
4465
|
+
const target = direction === "first" ? menuItems[0] : menuItems[menuItems.length - 1];
|
|
4466
|
+
if (target) {
|
|
4467
|
+
target.focus();
|
|
4468
|
+
return true;
|
|
4469
|
+
}
|
|
4470
|
+
getTriggerElement(key)?.focus();
|
|
4471
|
+
return false;
|
|
4472
|
+
}, [getEnabledMenuItems, getTriggerElement]);
|
|
4377
4473
|
const openSubMenu = react.useCallback((key) => {
|
|
4378
4474
|
if (hoverCloseTimeoutRef.current) {
|
|
4379
4475
|
clearTimeout(hoverCloseTimeoutRef.current);
|
|
@@ -4416,22 +4512,101 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4416
4512
|
hoverCloseTimeoutRef.current = null;
|
|
4417
4513
|
}
|
|
4418
4514
|
}, []);
|
|
4515
|
+
const closeAllSubMenus = react.useCallback(() => {
|
|
4516
|
+
if (hoverCloseTimeoutRef.current) {
|
|
4517
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
4518
|
+
hoverCloseTimeoutRef.current = null;
|
|
4519
|
+
}
|
|
4520
|
+
pendingKeyboardFocusKeyRef.current = null;
|
|
4521
|
+
setSubMenuOpen({});
|
|
4522
|
+
}, []);
|
|
4419
4523
|
const [activeMainKey, activeSubKey] = react.useMemo(() => {
|
|
4420
4524
|
if (!activeKey) return ["", ""];
|
|
4421
4525
|
const idx = activeKey.indexOf(SUB_KEY_SEP);
|
|
4422
4526
|
if (idx >= 0) return [activeKey.slice(0, idx), activeKey.slice(idx + 2)];
|
|
4423
4527
|
return [String(activeKey), ""];
|
|
4424
4528
|
}, [activeKey]);
|
|
4529
|
+
/** While a horizontal flyout is open, sibling tab controls use roving `tabIndex` so Tab cannot reach them. */
|
|
4530
|
+
const horizontalFlyoutOpenKey = react.useMemo(() => {
|
|
4531
|
+
if (tabPlacement !== "horizontal") return;
|
|
4532
|
+
return items.find((i) => Boolean(i.subItems?.length && subMenuOpen[i.key]))?.key;
|
|
4533
|
+
}, [
|
|
4534
|
+
items,
|
|
4535
|
+
subMenuOpen,
|
|
4536
|
+
tabPlacement
|
|
4537
|
+
]);
|
|
4425
4538
|
/** Expand vertical groups when the active route targets a sub-item. */
|
|
4426
4539
|
react.useEffect(() => {
|
|
4427
|
-
if (!activeSubKey) return;
|
|
4540
|
+
if (tabPlacement !== "vertical" || !activeSubKey) return;
|
|
4428
4541
|
items.forEach((item) => {
|
|
4429
4542
|
if (item.subItems?.some((s) => s.key === activeSubKey)) setSubMenuOpen((prev) => ({
|
|
4430
4543
|
...prev,
|
|
4431
4544
|
[item.key]: true
|
|
4432
4545
|
}));
|
|
4433
4546
|
});
|
|
4434
|
-
}, [
|
|
4547
|
+
}, [
|
|
4548
|
+
activeSubKey,
|
|
4549
|
+
items,
|
|
4550
|
+
tabPlacement
|
|
4551
|
+
]);
|
|
4552
|
+
react.useEffect(() => {
|
|
4553
|
+
const key = pendingKeyboardFocusKeyRef.current;
|
|
4554
|
+
if (!key || !subMenuOpen[key] || tabPlacement !== "horizontal") return;
|
|
4555
|
+
const menuContent = menuContentRefs.current[key];
|
|
4556
|
+
const focusFirstMenuItem = () => {
|
|
4557
|
+
const items = getEnabledMenuItems(key);
|
|
4558
|
+
const target = items.find((element) => element.getAttribute("aria-current") !== "page") ?? items[0];
|
|
4559
|
+
if (target) {
|
|
4560
|
+
target.focus();
|
|
4561
|
+
pendingKeyboardFocusKeyRef.current = null;
|
|
4562
|
+
return true;
|
|
4563
|
+
}
|
|
4564
|
+
return false;
|
|
4565
|
+
};
|
|
4566
|
+
if (focusFirstMenuItem()) return;
|
|
4567
|
+
if (!menuContent) return;
|
|
4568
|
+
const observer = new MutationObserver(() => {
|
|
4569
|
+
if (focusFirstMenuItem()) observer.disconnect();
|
|
4570
|
+
});
|
|
4571
|
+
observer.observe(menuContent, {
|
|
4572
|
+
childList: true,
|
|
4573
|
+
subtree: true
|
|
4574
|
+
});
|
|
4575
|
+
return () => {
|
|
4576
|
+
observer.disconnect();
|
|
4577
|
+
};
|
|
4578
|
+
}, [
|
|
4579
|
+
getEnabledMenuItems,
|
|
4580
|
+
subMenuOpen,
|
|
4581
|
+
tabPlacement
|
|
4582
|
+
]);
|
|
4583
|
+
const closeHorizontalSubMenuFromKeyboard = react.useCallback((key) => {
|
|
4584
|
+
pendingKeyboardFocusKeyRef.current = null;
|
|
4585
|
+
closeAllSubMenus();
|
|
4586
|
+
getTriggerElement(key)?.focus();
|
|
4587
|
+
}, [closeAllSubMenus, getTriggerElement]);
|
|
4588
|
+
const handleOpenSubMenuTabKey = react.useCallback((event, key) => {
|
|
4589
|
+
const menuItems = getEnabledMenuItems(key);
|
|
4590
|
+
if (menuItems.length === 0) {
|
|
4591
|
+
event.preventDefault();
|
|
4592
|
+
event.stopPropagation();
|
|
4593
|
+
getTriggerElement(key)?.focus();
|
|
4594
|
+
return;
|
|
4595
|
+
}
|
|
4596
|
+
const activeElement = document.activeElement;
|
|
4597
|
+
const currentIndex = activeElement instanceof HTMLElement ? menuItems.indexOf(activeElement) : -1;
|
|
4598
|
+
event.preventDefault();
|
|
4599
|
+
event.stopPropagation();
|
|
4600
|
+
if (currentIndex === -1) {
|
|
4601
|
+
focusMenuItem(key, event.shiftKey ? "last" : "first");
|
|
4602
|
+
return;
|
|
4603
|
+
}
|
|
4604
|
+
menuItems[(currentIndex + (event.shiftKey ? -1 : 1) + menuItems.length) % menuItems.length]?.focus();
|
|
4605
|
+
}, [
|
|
4606
|
+
focusMenuItem,
|
|
4607
|
+
getEnabledMenuItems,
|
|
4608
|
+
getTriggerElement
|
|
4609
|
+
]);
|
|
4435
4610
|
const handleTabClick = (key, event) => {
|
|
4436
4611
|
const item = items.find((i) => i.key === key);
|
|
4437
4612
|
if (item?.disabled) {
|
|
@@ -4444,9 +4619,40 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4444
4619
|
const handleKeyDown = (event, key) => {
|
|
4445
4620
|
const itemIndex = items.findIndex((i) => i.key === key);
|
|
4446
4621
|
if (itemIndex === -1) return;
|
|
4622
|
+
const item = items[itemIndex];
|
|
4623
|
+
const hasSubItems = Boolean(item?.subItems?.length);
|
|
4447
4624
|
let nextIndex = itemIndex;
|
|
4448
4625
|
const isHorizontal = tabPlacement === "horizontal";
|
|
4449
4626
|
const isVertical = tabPlacement === "vertical";
|
|
4627
|
+
if (hasSubItems && isHorizontal && (event.key === "Enter" || event.key === " ")) {
|
|
4628
|
+
event.preventDefault();
|
|
4629
|
+
event.stopPropagation();
|
|
4630
|
+
if (subMenuOpen[key]) {
|
|
4631
|
+
pendingKeyboardFocusKeyRef.current = null;
|
|
4632
|
+
closeSubMenu(key);
|
|
4633
|
+
} else {
|
|
4634
|
+
pendingKeyboardFocusKeyRef.current = key;
|
|
4635
|
+
openSubMenu(key);
|
|
4636
|
+
}
|
|
4637
|
+
return;
|
|
4638
|
+
}
|
|
4639
|
+
if (hasSubItems && isHorizontal && event.key === "Escape") {
|
|
4640
|
+
event.preventDefault();
|
|
4641
|
+
event.stopPropagation();
|
|
4642
|
+
closeHorizontalSubMenuFromKeyboard(key);
|
|
4643
|
+
return;
|
|
4644
|
+
}
|
|
4645
|
+
if (hasSubItems && isHorizontal && subMenuOpen[key] && event.key === "Tab") {
|
|
4646
|
+
handleOpenSubMenuTabKey(event, key);
|
|
4647
|
+
return;
|
|
4648
|
+
}
|
|
4649
|
+
if (hasSubItems && isHorizontal && subMenuOpen[key]) {
|
|
4650
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "Home" || event.key === "End") {
|
|
4651
|
+
event.preventDefault();
|
|
4652
|
+
event.stopPropagation();
|
|
4653
|
+
}
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4450
4656
|
if (isHorizontal) {
|
|
4451
4657
|
if (event.key === "ArrowLeft") {
|
|
4452
4658
|
event.preventDefault();
|
|
@@ -4510,6 +4716,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4510
4716
|
const isSubMenuOpen = hasSubItems && Boolean(subMenuOpen[item.key]);
|
|
4511
4717
|
/** Only one `aria-current="page"` in the nav: parent tab when no child route; sub-items set it when a child is current. */
|
|
4512
4718
|
const ariaCurrentPage = item.key === activeMainKey && (!hasSubItems || !activeSubKey);
|
|
4719
|
+
const computedTabIndex = tabPlacement === "horizontal" && horizontalFlyoutOpenKey != null ? !item.disabled && item.key === horizontalFlyoutOpenKey && hasSubItems ? 0 : -1 : void 0;
|
|
4513
4720
|
const commonTabProps = {
|
|
4514
4721
|
"data-tab-key": item.key,
|
|
4515
4722
|
"data-indent": item.indent || void 0,
|
|
@@ -4519,7 +4726,8 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4519
4726
|
"data-disabled": item.disabled || void 0,
|
|
4520
4727
|
id: `tabnav-${item.key}`,
|
|
4521
4728
|
className: TabNav_module_default.tab,
|
|
4522
|
-
...optionalAriaLabelProps(item)
|
|
4729
|
+
...optionalAriaLabelProps(item),
|
|
4730
|
+
...computedTabIndex !== void 0 ? { tabIndex: computedTabIndex } : {}
|
|
4523
4731
|
};
|
|
4524
4732
|
if (hasSubItems && tabPlacement === "vertical") {
|
|
4525
4733
|
const expanded = Boolean(subMenuOpen[item.key]);
|
|
@@ -4613,46 +4821,53 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4613
4821
|
const trigger = /* @__PURE__ */ react.createElement("button", {
|
|
4614
4822
|
type: "button",
|
|
4615
4823
|
...commonTabProps,
|
|
4824
|
+
"aria-expanded": isSubMenuOpen,
|
|
4825
|
+
"aria-haspopup": "menu",
|
|
4616
4826
|
"aria-current": ariaCurrentPage ? "page" : void 0,
|
|
4617
4827
|
disabled: item.disabled,
|
|
4618
4828
|
onMouseEnter: () => openSubMenu(item.key),
|
|
4619
4829
|
onMouseLeave: () => scheduleCloseSubMenu(item.key),
|
|
4620
|
-
onPointerDown: () => {
|
|
4621
|
-
pointerDownOnTriggerRef.current = true;
|
|
4622
|
-
},
|
|
4623
|
-
onPointerUp: () => {
|
|
4624
|
-
pointerDownOnTriggerRef.current = false;
|
|
4625
|
-
},
|
|
4626
|
-
onFocus: () => {
|
|
4627
|
-
if (!pointerDownOnTriggerRef.current) openSubMenu(item.key);
|
|
4628
|
-
},
|
|
4629
4830
|
onKeyDown: (e) => handleKeyDown(e, item.key)
|
|
4630
4831
|
}, renderTabContent(item));
|
|
4631
4832
|
return /* @__PURE__ */ react.createElement(Menu, {
|
|
4632
4833
|
key: item.key,
|
|
4633
4834
|
trigger,
|
|
4835
|
+
trapFocus: true,
|
|
4634
4836
|
itemHoverAppearance: "accent",
|
|
4635
4837
|
itemActiveAccentBar: false,
|
|
4636
4838
|
itemActiveLabelSemibold: false,
|
|
4637
4839
|
panelPadding: "none",
|
|
4638
4840
|
popoverOffset: 1,
|
|
4639
4841
|
open: subMenuOpen[item.key],
|
|
4640
|
-
onOpenChange: (open) =>
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4842
|
+
onOpenChange: (open) => {
|
|
4843
|
+
if (open) {
|
|
4844
|
+
openSubMenu(item.key);
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
pendingKeyboardFocusKeyRef.current = null;
|
|
4848
|
+
closeSubMenu(item.key);
|
|
4849
|
+
},
|
|
4644
4850
|
contentProps: {
|
|
4851
|
+
ref: (node) => {
|
|
4852
|
+
menuContentRefs.current[item.key] = node;
|
|
4853
|
+
},
|
|
4645
4854
|
onMouseEnter: () => {
|
|
4646
4855
|
cancelScheduledClose();
|
|
4647
4856
|
openSubMenu(item.key);
|
|
4648
4857
|
},
|
|
4649
|
-
onMouseLeave: () => closeSubMenu(item.key)
|
|
4858
|
+
onMouseLeave: () => closeSubMenu(item.key),
|
|
4859
|
+
onKeyDownCapture: (event) => {
|
|
4860
|
+
if (event.key === "Escape") {
|
|
4861
|
+
event.preventDefault();
|
|
4862
|
+
event.stopPropagation();
|
|
4863
|
+
closeHorizontalSubMenuFromKeyboard(item.key);
|
|
4864
|
+
return;
|
|
4865
|
+
}
|
|
4866
|
+
if (event.key === "Tab") handleOpenSubMenuTabKey(event, item.key);
|
|
4867
|
+
}
|
|
4650
4868
|
}
|
|
4651
4869
|
}, item.subItems.map((sub) => {
|
|
4652
|
-
const closeMenu = () =>
|
|
4653
|
-
...prev,
|
|
4654
|
-
[item.key]: false
|
|
4655
|
-
}));
|
|
4870
|
+
const closeMenu = () => closeAllSubMenus();
|
|
4656
4871
|
return /* @__PURE__ */ react.createElement(Menu.Item, {
|
|
4657
4872
|
key: sub.key,
|
|
4658
4873
|
label: sub.name,
|
|
@@ -4681,7 +4896,7 @@ const TabNav = ({ activeKey, onTabClick, items, tabPlacement = "horizontal", cen
|
|
|
4681
4896
|
role: "link",
|
|
4682
4897
|
"aria-disabled": item.disabled,
|
|
4683
4898
|
"aria-current": ariaCurrentPage ? "page" : void 0,
|
|
4684
|
-
tabIndex: 0,
|
|
4899
|
+
tabIndex: item.disabled ? -1 : computedTabIndex ?? 0,
|
|
4685
4900
|
onKeyDown: (e) => {
|
|
4686
4901
|
if (e.key === "Enter" || e.key === " ") {
|
|
4687
4902
|
e.preventDefault();
|
|
@@ -4749,11 +4964,13 @@ var Tooltip_module_default = {
|
|
|
4749
4964
|
};
|
|
4750
4965
|
//#endregion
|
|
4751
4966
|
//#region src/components/Tooltip/Tooltip.tsx
|
|
4967
|
+
const TOOLTIP_HOVER_SHOW_DELAY_MS = 500;
|
|
4752
4968
|
/**
|
|
4753
4969
|
* A component to display additional information when hovering or focusing on an interactive element.
|
|
4754
4970
|
*/
|
|
4755
4971
|
function Tooltip({ children, title, shortcut, getContainer, placement = "bottom", isDisabled = false, FORCE__className: classNameOverride, ...props }) {
|
|
4756
4972
|
return /* @__PURE__ */ react.createElement(react_aria_components.TooltipTrigger, {
|
|
4973
|
+
delay: TOOLTIP_HOVER_SHOW_DELAY_MS,
|
|
4757
4974
|
isDisabled,
|
|
4758
4975
|
shouldCloseOnPress: false
|
|
4759
4976
|
}, children, /* @__PURE__ */ react.createElement(react_aria.UNSAFE_PortalProvider, { getContainer }, /* @__PURE__ */ react.createElement(react_aria_components.Tooltip, {
|