@geotab/zenith 3.17.0-beta.2 → 3.17.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/button/button.d.ts +1 -2
- package/dist/button/button.js +5 -22
- package/dist/button/buttonDisabledProps.d.ts +35 -0
- package/dist/button/buttonDisabledProps.js +33 -0
- package/dist/header/headerButton.d.ts +15 -1
- package/dist/header/headerButton.js +11 -6
- package/dist/index.css +2 -6
- package/dist/menu/components/menuButton.js +40 -58
- package/dist/pageHeader/pageHeaderButton.d.ts +15 -1
- package/dist/pageHeader/pageHeaderButton.js +8 -6
- package/dist/tooltip/tooltip.d.ts +5 -3
- package/dist/tooltip/tooltip.js +11 -9
- package/esm/button/button.d.ts +1 -2
- package/esm/button/button.js +5 -22
- package/esm/button/buttonDisabledProps.d.ts +35 -0
- package/esm/button/buttonDisabledProps.js +29 -0
- package/esm/header/headerButton.d.ts +15 -1
- package/esm/header/headerButton.js +11 -6
- package/esm/menu/components/menuButton.js +40 -58
- package/esm/pageHeader/pageHeaderButton.d.ts +15 -1
- package/esm/pageHeader/pageHeaderButton.js +8 -6
- package/esm/tooltip/tooltip.d.ts +5 -3
- package/esm/tooltip/tooltip.js +11 -9
- package/package.json +4 -1
package/dist/button/button.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
|
|
|
9
9
|
title?: string;
|
|
10
10
|
type?: TButton;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
-
focusableWhenDisabled?: boolean;
|
|
13
12
|
onClick?: React.MouseEventHandler;
|
|
14
13
|
onBlur?: React.FocusEventHandler;
|
|
15
14
|
onMouseOver?: (e: React.MouseEvent<HTMLElement>) => void;
|
|
@@ -28,6 +27,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
|
|
|
28
27
|
dataAnalyticsId?: string;
|
|
29
28
|
}
|
|
30
29
|
export declare const Button: {
|
|
31
|
-
({ className, type, disabled,
|
|
30
|
+
({ className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId, ...other }: IButton): import("react/jsx-runtime").JSX.Element;
|
|
32
31
|
displayName: string;
|
|
33
32
|
};
|
package/dist/button/button.js
CHANGED
|
@@ -18,36 +18,19 @@ const classNames_1 = require("../commonHelpers/classNames/classNames");
|
|
|
18
18
|
const useDriveClassName_1 = require("../utils/theme/useDriveClassName");
|
|
19
19
|
const filterGridProps_1 = require("../gridLayout/utils/filterGridProps");
|
|
20
20
|
const useMobileClassName_1 = require("../utils/theme/useMobileClassName");
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
event.stopPropagation();
|
|
24
|
-
};
|
|
25
|
-
// Guards on `disabled` alone, not just `focusableWhenDisabled`, so clicks are blocked in JS regardless of
|
|
26
|
-
// CSS. `onKeyDown`/`onKeyUp` stay unguarded — Tooltip needs them.
|
|
27
|
-
const getDisabledGuardedHandlers = (disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp) => ({
|
|
28
|
-
onClick: disabled ? blockActivation : onClick,
|
|
29
|
-
onMouseDown: isFocusableWhenDisabled ? blockActivation : onMouseDown,
|
|
30
|
-
onMouseUp: isFocusableWhenDisabled ? blockActivation : onMouseUp
|
|
31
|
-
});
|
|
32
|
-
const getButtonClassName = (type, disabled, isFocusableWhenDisabled, mobileOrDriveClasses, className) => {
|
|
21
|
+
const Button = (_a) => {
|
|
22
|
+
var { className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
|
|
33
23
|
const modifierClassName = type && type !== buttonType_1.ButtonType.Secondary ? `zen-button--${type}` : "";
|
|
34
24
|
const disabledClassName = disabled ? "zen-button--disabled" : "";
|
|
35
|
-
const disabledFocusableClassName = isFocusableWhenDisabled ? "zen-button--disabled-focusable" : "";
|
|
36
|
-
return (0, classNames_1.classNames)(["zen-button", mobileOrDriveClasses, modifierClassName, disabledClassName, disabledFocusableClassName, className !== null && className !== void 0 ? className : ""]);
|
|
37
|
-
};
|
|
38
|
-
const Button = (_a) => {
|
|
39
|
-
var { className, type, disabled, focusableWhenDisabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "focusableWhenDisabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
|
|
40
|
-
const isFocusableWhenDisabled = Boolean(disabled) && Boolean(focusableWhenDisabled);
|
|
41
25
|
const driveClasses = (0, useDriveClassName_1.useDriveClassName)("zen-button");
|
|
42
26
|
const ariaLabelValue = ariaLabel || title;
|
|
43
27
|
const mobileClasses = (0, useMobileClassName_1.useMobileClassName)("zen-button");
|
|
44
28
|
const commonArgs = Object.assign(Object.assign(Object.assign({ id,
|
|
45
|
-
title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className:
|
|
46
|
-
const { onClick: onClickWithDisabledGuard, onMouseDown: onMouseDownWithDisabledGuard, onMouseUp: onMouseUpWithDisabledGuard } = getDisabledGuardedHandlers(disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp);
|
|
29
|
+
title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: (0, classNames_1.classNames)(["zen-button", mobileClasses || driveClasses || "", modifierClassName, disabledClassName, className !== null && className !== void 0 ? className : ""]) });
|
|
47
30
|
if (link) {
|
|
48
|
-
return ((0, jsx_runtime_1.jsx)("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link,
|
|
31
|
+
return ((0, jsx_runtime_1.jsx)("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link, "aria-disabled": disabled, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onClick: onClick }, commonArgs, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
|
|
49
32
|
}
|
|
50
|
-
return ((0, jsx_runtime_1.jsx)("button", Object.assign({ name: name, onBlur: onBlur, onClick:
|
|
33
|
+
return ((0, jsx_runtime_1.jsx)("button", Object.assign({ name: name, onBlur: onBlur, onClick: onClick, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ref: ref, type: htmlType, disabled: disabled, role: role }, commonArgs, (0, filterGridProps_1.filterGridProps)(other), { children: children })));
|
|
51
34
|
};
|
|
52
35
|
exports.Button = Button;
|
|
53
36
|
exports.Button.displayName = "Button";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export declare const BUTTON_DISABLED_CLASS = "zen-button--disabled";
|
|
3
|
+
export declare const BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
|
|
4
|
+
export interface IButtonDisabledOptions {
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Keeps a disabled button in the tab order and hoverable, using `aria-disabled` instead of the native
|
|
8
|
+
* `disabled` attribute. Has no effect while the button is enabled.
|
|
9
|
+
*/
|
|
10
|
+
focusable?: boolean;
|
|
11
|
+
/** The `link` prop of the button, a link without `href` is not tabbable, so it needs an explicit `tabIndex`. */
|
|
12
|
+
link?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IButtonDisabledProps {
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
"aria-disabled"?: boolean;
|
|
17
|
+
tabIndex?: number;
|
|
18
|
+
className?: string;
|
|
19
|
+
onClick?: (event: React.SyntheticEvent) => void;
|
|
20
|
+
onMouseDown?: (event: React.SyntheticEvent) => void;
|
|
21
|
+
onMouseUp?: (event: React.SyntheticEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
|
|
25
|
+
*
|
|
26
|
+
* - enabled: `{ disabled }`
|
|
27
|
+
* - disabled: native `disabled`, the button leaves the tab order
|
|
28
|
+
* - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
|
|
29
|
+
*
|
|
30
|
+
* `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
|
|
31
|
+
* `disabled` attribute fire a click, which is blocked here.
|
|
32
|
+
*
|
|
33
|
+
* The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
|
|
34
|
+
*/
|
|
35
|
+
export declare const getButtonDisabledProps: ({ disabled, focusable, link }: IButtonDisabledOptions) => IButtonDisabledProps;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getButtonDisabledProps = exports.BUTTON_DISABLED_FOCUSABLE_CLASS = exports.BUTTON_DISABLED_CLASS = void 0;
|
|
4
|
+
const classNames_1 = require("../commonHelpers/classNames/classNames");
|
|
5
|
+
// `button.less` styles these classes: `--disabled-focusable` keeps pointer events so hover/tooltip still work.
|
|
6
|
+
exports.BUTTON_DISABLED_CLASS = "zen-button--disabled";
|
|
7
|
+
exports.BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
|
|
8
|
+
const blockActivation = (event) => {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
event.stopPropagation();
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
|
|
14
|
+
*
|
|
15
|
+
* - enabled: `{ disabled }`
|
|
16
|
+
* - disabled: native `disabled`, the button leaves the tab order
|
|
17
|
+
* - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
|
|
18
|
+
*
|
|
19
|
+
* `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
|
|
20
|
+
* `disabled` attribute fire a click, which is blocked here.
|
|
21
|
+
*
|
|
22
|
+
* The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
|
|
23
|
+
*/
|
|
24
|
+
const getButtonDisabledProps = ({ disabled, focusable, link }) => {
|
|
25
|
+
if (!disabled || !focusable) {
|
|
26
|
+
return { disabled };
|
|
27
|
+
}
|
|
28
|
+
const hasLink = Boolean(link);
|
|
29
|
+
return Object.assign(Object.assign({}, (hasLink ? { disabled: true, tabIndex: 0 } : { "aria-disabled": true })), {
|
|
30
|
+
// Button adds `zen-button--disabled` to a disabled link itself.
|
|
31
|
+
className: (0, classNames_1.classNames)([hasLink ? "" : exports.BUTTON_DISABLED_CLASS, exports.BUTTON_DISABLED_FOCUSABLE_CLASS]), onClick: blockActivation, onMouseDown: blockActivation, onMouseUp: blockActivation });
|
|
32
|
+
};
|
|
33
|
+
exports.getButtonDisabledProps = getButtonDisabledProps;
|
|
@@ -2,6 +2,7 @@ import { IButton } from "../button/button";
|
|
|
2
2
|
import { TButtonIconPosition } from "../textIconButton/textIconButton";
|
|
3
3
|
import { TSvgIconSize } from "../svgIcon/svgIconSize";
|
|
4
4
|
import { IIcon } from "../icons/icon";
|
|
5
|
+
import { TAlignment } from "../absolute/absolute";
|
|
5
6
|
export interface IHeaderButton extends Omit<IButton, "onClick"> {
|
|
6
7
|
id: string;
|
|
7
8
|
important?: boolean;
|
|
@@ -11,10 +12,23 @@ export interface IHeaderButton extends Omit<IButton, "onClick"> {
|
|
|
11
12
|
iconPosition?: TButtonIconPosition;
|
|
12
13
|
iconClasses?: string;
|
|
13
14
|
iconSize?: TSvgIconSize;
|
|
15
|
+
/**
|
|
16
|
+
* Tooltip content shown only while the button is `disabled`.
|
|
17
|
+
*
|
|
18
|
+
* When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
|
|
19
|
+
* `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
|
|
20
|
+
* In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
|
|
21
|
+
* button is enabled.
|
|
22
|
+
*/
|
|
23
|
+
disabledTooltip?: React.ReactNode;
|
|
14
24
|
onClick?: (id: string, event: React.MouseEvent) => void;
|
|
15
25
|
}
|
|
26
|
+
interface IHeaderButtonInternal {
|
|
27
|
+
tooltipAlignment?: TAlignment;
|
|
28
|
+
}
|
|
16
29
|
export declare const HeaderButtonDisplayName = "HeaderButton";
|
|
17
30
|
export declare const HeaderButton: {
|
|
18
|
-
({ id, children, icon, iconOpen, iconPosition, className, important, onClick, inContextMenu, disabled,
|
|
31
|
+
({ id, children, icon, iconOpen, iconPosition, className, important, onClick, inContextMenu, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IHeaderButton & IHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
|
|
19
32
|
displayName: string;
|
|
20
33
|
};
|
|
34
|
+
export {};
|
|
@@ -16,14 +16,19 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
16
16
|
const button_1 = require("../button/button");
|
|
17
17
|
const textIconButton_1 = require("../textIconButton/textIconButton");
|
|
18
18
|
const classNames_1 = require("../commonHelpers/classNames/classNames");
|
|
19
|
+
const tooltip_1 = require("../tooltip/tooltip");
|
|
20
|
+
const buttonDisabledProps_1 = require("../button/buttonDisabledProps");
|
|
19
21
|
exports.HeaderButtonDisplayName = "HeaderButton";
|
|
20
22
|
const HeaderButton = (_a) => {
|
|
21
|
-
var { id, children, icon, iconOpen, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabled,
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
var { id, children, icon, iconOpen, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "iconPosition", "className", "important", "onClick", "inContextMenu", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
|
|
24
|
+
const tooltip = disabled ? disabledTooltip : undefined;
|
|
25
|
+
// Native `disabled` takes a button out of the tab order and blocks hover, so a tooltip could never be reached.
|
|
26
|
+
// Only a disabled button that has a tooltip to show is kept focusable, via `aria-disabled`.
|
|
27
|
+
const _b = (0, buttonDisabledProps_1.getButtonDisabledProps)({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
|
|
28
|
+
const fullClassName = (0, classNames_1.classNames)(["zen-header-button", className, disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
|
|
29
|
+
const props = Object.assign(Object.assign(Object.assign({}, restProps), { link, "data-important": important ? "1" : "0" }), disabledProps);
|
|
30
|
+
const button = icon ? ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : ((0, jsx_runtime_1.jsx)(button_1.Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
|
|
31
|
+
return tooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
|
|
27
32
|
};
|
|
28
33
|
exports.HeaderButton = HeaderButton;
|
|
29
34
|
exports.HeaderButton.displayName = exports.HeaderButtonDisplayName;
|
package/dist/index.css
CHANGED
|
@@ -4007,8 +4007,8 @@ html:lang(ar) .zen-menu-button__action {
|
|
|
4007
4007
|
padding-block: 0.75rem;
|
|
4008
4008
|
padding-inline: 1rem 0.5rem;
|
|
4009
4009
|
}
|
|
4010
|
-
.zen-menu-button__action:focus
|
|
4011
|
-
.zen-menu-button__action:focus-visible
|
|
4010
|
+
.zen-menu-button__action:focus,
|
|
4011
|
+
.zen-menu-button__action:focus-visible,
|
|
4012
4012
|
.zen-menu-button__action:hover:not(.zen-menu-button__action--disabled):not(.zen-menu-button__action--selected):not(.zen-menu-button__action--active) {
|
|
4013
4013
|
color: var(--zen-menu-action-color);
|
|
4014
4014
|
fill: var(--zen-menu-action-fill);
|
|
@@ -4083,10 +4083,6 @@ html:lang(ar) .zen-menu-button__action {
|
|
|
4083
4083
|
.zen-menu-button__action.zen-menu-button__action {
|
|
4084
4084
|
outline: none;
|
|
4085
4085
|
}
|
|
4086
|
-
.zen-menu-button__action--disabled:focus-visible {
|
|
4087
|
-
outline: 1px solid var(--borders-form-field--active);
|
|
4088
|
-
outline-offset: -2px;
|
|
4089
|
-
}
|
|
4090
4086
|
.zen-menu-button__icon {
|
|
4091
4087
|
flex: 0 0 auto;
|
|
4092
4088
|
}
|
|
@@ -13,60 +13,6 @@ const useDrive_1 = require("../../utils/theme/useDrive");
|
|
|
13
13
|
const getMenuButtonState_1 = require("../utils/getMenuButtonState");
|
|
14
14
|
const isSafeHref_1 = require("../utils/isSafeHref");
|
|
15
15
|
const getSafeRel_1 = require("../utils/getSafeRel");
|
|
16
|
-
// Templates below drop native `disabled` to stay focusable/hoverable; this guard blocks activation instead.
|
|
17
|
-
const handleDisabledGuardedClick = (e, disabled, id, onClick) => {
|
|
18
|
-
if (disabled) {
|
|
19
|
-
e.preventDefault();
|
|
20
|
-
e.stopPropagation();
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
24
|
-
};
|
|
25
|
-
// Skip Space-to-activate when disabled, so it doesn't flash `--active` or self-click via `linkEl.click()`.
|
|
26
|
-
const createDisabledGuardedLinkKeyEvents = (disabled) => ({
|
|
27
|
-
onKeyDown: (e) => {
|
|
28
|
-
if (disabled || e.key !== " ") {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
const linkEl = e.target;
|
|
33
|
-
linkEl.dataset.spaceDown = "1";
|
|
34
|
-
linkEl.classList.add("zen-menu-button__action--active");
|
|
35
|
-
},
|
|
36
|
-
onKeyUp: (e) => {
|
|
37
|
-
if (disabled || e.key !== " ") {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
const linkEl = e.target;
|
|
42
|
-
linkEl.classList.remove("zen-menu-button__action--active");
|
|
43
|
-
if (linkEl.dataset.spaceDown) {
|
|
44
|
-
delete linkEl.dataset.spaceDown;
|
|
45
|
-
linkEl.click();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
const getMenuButtonActionClassName = (hasChildren, hasState, isActive, disabled, destructiveClass, driveClasses) => (0, classNames_1.classNames)([
|
|
50
|
-
"zen-menu-button__action",
|
|
51
|
-
"zen-button",
|
|
52
|
-
"zen-caption",
|
|
53
|
-
hasChildren ? "zen-menu-button__action--has-children" : "",
|
|
54
|
-
hasState && isActive ? "zen-menu-button__action--active" : "",
|
|
55
|
-
disabled ? "zen-menu-button__action--disabled" : "",
|
|
56
|
-
disabled ? "zen-button--disabled" : "",
|
|
57
|
-
disabled ? "zen-button--disabled-focusable" : "",
|
|
58
|
-
destructiveClass,
|
|
59
|
-
driveClasses || ""
|
|
60
|
-
]);
|
|
61
|
-
const getMenuLinkActionClassName = (disabled, destructiveClass, driveClasses) => (0, classNames_1.classNames)(["zen-menu-button__action", "zen-caption", disabled ? "zen-menu-button__action--disabled" : "", destructiveClass, driveClasses || ""]);
|
|
62
|
-
// An <a> without `href` isn't tabbable, so `tabIndex="0"` keeps a disabled link item in the tab order.
|
|
63
|
-
const getMenuLinkAccessibilityProps = (disabled, link) => ({
|
|
64
|
-
href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link,
|
|
65
|
-
tabIndex: disabled ? 0 : undefined,
|
|
66
|
-
"aria-disabled": disabled
|
|
67
|
-
});
|
|
68
|
-
const getMenuItemRole = (hasState) => (hasState ? "menuitemcheckbox" : "menuitem");
|
|
69
|
-
const getMenuItemAriaChecked = (hasState, isActive) => (hasState ? isActive : undefined);
|
|
70
16
|
const createMenuIcon = (iconComponent, size, className) => iconComponent ? (0, react_1.createElement)(iconComponent, { size, className }) : null;
|
|
71
17
|
const resolveTarget = (target, iconOpen) => target !== null && target !== void 0 ? target : (iconOpen ? "_blank" : undefined);
|
|
72
18
|
const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destructive, name, link, target, rel, className = "", active = null, ref, dataAnalyticsId }) => {
|
|
@@ -77,11 +23,47 @@ const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destru
|
|
|
77
23
|
const destructiveClass = destructive ? "zen-menu-button__action--destructive" : "";
|
|
78
24
|
const effectiveTarget = resolveTarget(target, iconOpen);
|
|
79
25
|
const ChevronIcon = (0, useDirectionalIcon_1.useDirectionalIcon)(iconChevronRight_1.IconChevronRight, iconChevronLeft_1.IconChevronLeft);
|
|
80
|
-
const linkEvents = (0, react_1.useMemo)(() =>
|
|
81
|
-
|
|
26
|
+
const linkEvents = (0, react_1.useMemo)(() => ({
|
|
27
|
+
onKeyDown: e => {
|
|
28
|
+
if (e.key === " ") {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
const linkEl = e.target;
|
|
31
|
+
linkEl.dataset.spaceDown = "1";
|
|
32
|
+
linkEl.classList.add("zen-menu-button__action--active");
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
onKeyUp: e => {
|
|
36
|
+
if (e.key === " ") {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
const linkEl = e.target;
|
|
39
|
+
linkEl.classList.remove("zen-menu-button__action--active");
|
|
40
|
+
if (linkEl.dataset.spaceDown) {
|
|
41
|
+
delete linkEl.dataset.spaceDown;
|
|
42
|
+
linkEl.click();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}), []);
|
|
47
|
+
const onClickHandler = (0, react_1.useCallback)((e) => {
|
|
48
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
49
|
+
}, [id, onClick]);
|
|
82
50
|
const iconAfterElement = createMenuIcon(iconOpen ? iconOpen_1.IconOpen : undefined, iconSize, "zen-menu-button__icon-after");
|
|
83
|
-
const buttonTemplate = ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-id": id, ref: ref,
|
|
84
|
-
|
|
51
|
+
const buttonTemplate = ((0, jsx_runtime_1.jsxs)("button", { type: "button", "data-id": id, ref: ref, disabled: !!disabled, title: name, className: (0, classNames_1.classNames)([
|
|
52
|
+
"zen-menu-button__action",
|
|
53
|
+
"zen-button",
|
|
54
|
+
"zen-caption",
|
|
55
|
+
hasChildren ? "zen-menu-button__action--has-children" : "",
|
|
56
|
+
hasState && isActive ? "zen-menu-button__action--active" : "",
|
|
57
|
+
destructiveClass,
|
|
58
|
+
driveMenuButtonActionClasses || ""
|
|
59
|
+
]), role: hasState ? "menuitemcheckbox" : "menuitem", onClick: onClickHandler, "aria-checked": hasState ? isActive : undefined, "data-analytics-id": dataAnalyticsId, children: [createMenuIcon(icon, iconSize, (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__icon"])), (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && (0, jsx_runtime_1.jsx)(ChevronIcon, { size: iconSize, className: (0, classNames_1.classNames)(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
|
|
60
|
+
const linkTemplate = ((0, jsx_runtime_1.jsxs)("a", Object.assign({ "data-id": id, role: "menuitem", className: (0, classNames_1.classNames)([
|
|
61
|
+
"zen-menu-button__action",
|
|
62
|
+
"zen-caption",
|
|
63
|
+
disabled ? "zen-menu-button__action--disabled" : "",
|
|
64
|
+
destructiveClass,
|
|
65
|
+
driveMenuButtonActionClasses || ""
|
|
66
|
+
]), href: disabled || !(0, isSafeHref_1.isSafeHref)(link) ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: effectiveTarget, rel: (0, getSafeRel_1.getSafeRel)(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? (0, jsx_runtime_1.jsx)("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
|
|
85
67
|
return ((0, jsx_runtime_1.jsx)("li", { role: "presentation", className: (0, classNames_1.classNames)(["zen-menu-button", className]), children: link ? linkTemplate : buttonTemplate }));
|
|
86
68
|
};
|
|
87
69
|
exports.MenuButton = MenuButton;
|
|
@@ -2,6 +2,7 @@ import { IButton } from "../button/button";
|
|
|
2
2
|
import { TButtonIconPosition } from "../textIconButton/textIconButton";
|
|
3
3
|
import { TSvgIconSize } from "../svgIcon/svgIconSize";
|
|
4
4
|
import { IIcon } from "../icons/icon";
|
|
5
|
+
import { TAlignment } from "../absolute/absolute";
|
|
5
6
|
export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "title"> {
|
|
6
7
|
id: string;
|
|
7
8
|
icon: React.FC<IIcon>;
|
|
@@ -9,10 +10,23 @@ export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "t
|
|
|
9
10
|
iconPosition?: TButtonIconPosition;
|
|
10
11
|
iconClasses?: string;
|
|
11
12
|
iconSize?: TSvgIconSize;
|
|
13
|
+
/**
|
|
14
|
+
* Tooltip content shown only while the button is `disabled`.
|
|
15
|
+
*
|
|
16
|
+
* When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
|
|
17
|
+
* `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
|
|
18
|
+
* In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
|
|
19
|
+
* button is enabled.
|
|
20
|
+
*/
|
|
21
|
+
disabledTooltip?: React.ReactNode;
|
|
12
22
|
onClick?: (id: string, event: React.MouseEvent) => void;
|
|
13
23
|
}
|
|
24
|
+
interface IPageHeaderButtonInternal {
|
|
25
|
+
tooltipAlignment?: TAlignment;
|
|
26
|
+
}
|
|
14
27
|
export declare const PageHeaderButtonDisplayName = "PageHeaderButton";
|
|
15
28
|
export declare const PageHeaderButton: {
|
|
16
|
-
({ id, children, icon, iconPosition, className, onClick, title, disabled,
|
|
29
|
+
({ id, children, icon, iconPosition, className, onClick, title, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IPageHeaderButton & IPageHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
|
|
17
30
|
displayName: string;
|
|
18
31
|
};
|
|
32
|
+
export {};
|
|
@@ -15,14 +15,16 @@ exports.PageHeaderButton = exports.PageHeaderButtonDisplayName = void 0;
|
|
|
15
15
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
16
16
|
const textIconButton_1 = require("../textIconButton/textIconButton");
|
|
17
17
|
const classNames_1 = require("../commonHelpers/classNames/classNames");
|
|
18
|
+
const tooltip_1 = require("../tooltip/tooltip");
|
|
19
|
+
const buttonDisabledProps_1 = require("../button/buttonDisabledProps");
|
|
18
20
|
exports.PageHeaderButtonDisplayName = "PageHeaderButton";
|
|
19
21
|
const PageHeaderButton = (_a) => {
|
|
20
|
-
var { id, children, icon, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", onClick, title, disabled,
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
22
|
+
var { id, children, icon, iconPosition = textIconButton_1.ButtonIconPosition.Start, className = "", onClick, title, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
|
|
23
|
+
const tooltip = disabled ? disabledTooltip : undefined;
|
|
24
|
+
const _b = (0, buttonDisabledProps_1.getButtonDisabledProps)({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
|
|
25
|
+
const fullClassName = (0, classNames_1.classNames)(["zen-page-header-button", className || "", disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
|
|
26
|
+
const button = ((0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined, link: link }, restProps, disabledProps, { children: children })));
|
|
27
|
+
return tooltip ? ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
|
|
26
28
|
};
|
|
27
29
|
exports.PageHeaderButton = PageHeaderButton;
|
|
28
30
|
exports.PageHeaderButton.displayName = exports.PageHeaderButtonDisplayName;
|
|
@@ -5,11 +5,13 @@ import { TAlignment } from "../absolute/absolute";
|
|
|
5
5
|
import { IInlineTrigger } from "./components/inlineTrigger";
|
|
6
6
|
export type TooltipSize = "large" | "small" | "auto";
|
|
7
7
|
export type TMobileView = "default" | "mobileSheet";
|
|
8
|
+
interface ITriggerDisabledProps {
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
"aria-disabled"?: boolean | "true" | "false";
|
|
11
|
+
}
|
|
8
12
|
interface ITooltip extends IZenComponentProps {
|
|
9
|
-
trigger?: ReactElement<PropsWithChildren & {
|
|
13
|
+
trigger?: ReactElement<PropsWithChildren & ITriggerDisabledProps & {
|
|
10
14
|
onClick?: (e: React.MouseEvent) => void;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
focusableWhenDisabled?: boolean;
|
|
13
15
|
}>;
|
|
14
16
|
alignment?: TAlignment;
|
|
15
17
|
triggerClassName?: string;
|
package/dist/tooltip/tooltip.js
CHANGED
|
@@ -159,12 +159,14 @@ const getInitialDeviceType = () => {
|
|
|
159
159
|
}
|
|
160
160
|
return deviceType_1.DeviceType.Desktop;
|
|
161
161
|
};
|
|
162
|
+
// A disabled trigger (native `disabled`, or `aria-disabled` when it must stay focusable) never runs its own
|
|
163
|
+
// `onClick`, so the tooltip has to react to the click capture phase instead.
|
|
164
|
+
const isTriggerDisabled = props => Boolean(props === null || props === void 0 ? void 0 : props.disabled) || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === true || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === "true";
|
|
162
165
|
const EventWrapper = ({
|
|
163
166
|
children,
|
|
164
167
|
deviceType,
|
|
165
168
|
onKeyDownHandler,
|
|
166
169
|
handleUniversalClick,
|
|
167
|
-
isTriggerDisabledFocusable,
|
|
168
170
|
setTooltipVisible,
|
|
169
171
|
timerRef,
|
|
170
172
|
timerMouseLeaveRef,
|
|
@@ -173,8 +175,9 @@ const EventWrapper = ({
|
|
|
173
175
|
const isCurrentDeviceDesktop = deviceType === deviceType_1.DeviceType.Desktop;
|
|
174
176
|
const triggerProps = children.props;
|
|
175
177
|
const originalOnClick = triggerProps.onClick;
|
|
176
|
-
// A disabled
|
|
177
|
-
//
|
|
178
|
+
// A disabled trigger doesn't run its own `onClick`, so on mobile the tap is handled in the capture phase
|
|
179
|
+
// instead — see `mobileEvents` below.
|
|
180
|
+
const isDisabledTrigger = isTriggerDisabled(triggerProps);
|
|
178
181
|
const composedOnClick = (0, react_1.useCallback)(e => {
|
|
179
182
|
// Call original onClick first if it exists
|
|
180
183
|
if (originalOnClick) {
|
|
@@ -213,7 +216,7 @@ const EventWrapper = ({
|
|
|
213
216
|
}, [handleUniversalClick]);
|
|
214
217
|
const mobileEvents = Object.assign({
|
|
215
218
|
onKeyDown: mobileOnKeyDown
|
|
216
|
-
},
|
|
219
|
+
}, isDisabledTrigger ? {
|
|
217
220
|
onClickCapture: handleUniversalClick
|
|
218
221
|
} : {
|
|
219
222
|
onClick: composedOnClick
|
|
@@ -248,7 +251,7 @@ const Tooltip = ({
|
|
|
248
251
|
const [deviceType, setDeviceType] = (0, react_1.useState)(getInitialDeviceType);
|
|
249
252
|
const isMobile = deviceType === deviceType_1.DeviceType.Mobile;
|
|
250
253
|
const triggerProps = trigger === null || trigger === void 0 ? void 0 : trigger.props;
|
|
251
|
-
const
|
|
254
|
+
const isDisabledTrigger = isTriggerDisabled(triggerProps);
|
|
252
255
|
const tooltipId = (0, react_1.useId)();
|
|
253
256
|
const {
|
|
254
257
|
translate
|
|
@@ -279,7 +282,7 @@ const Tooltip = ({
|
|
|
279
282
|
}
|
|
280
283
|
};
|
|
281
284
|
const handleUniversalClick = (0, react_1.useCallback)(e => {
|
|
282
|
-
const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !
|
|
285
|
+
const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isDisabledTrigger;
|
|
283
286
|
if (deviceType === deviceType_1.DeviceType.Desktop) {
|
|
284
287
|
setTooltipVisible(currentVisible => {
|
|
285
288
|
if (currentVisible) {
|
|
@@ -288,14 +291,14 @@ const Tooltip = ({
|
|
|
288
291
|
return currentVisible;
|
|
289
292
|
});
|
|
290
293
|
} else {
|
|
291
|
-
if (!
|
|
294
|
+
if (!isDisabledTrigger) {
|
|
292
295
|
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
293
296
|
}
|
|
294
297
|
if (!hasExternalClickHandler) {
|
|
295
298
|
setTooltipVisible(isVisible => !isVisible);
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
|
-
}, [deviceType,
|
|
301
|
+
}, [deviceType, isDisabledTrigger, triggerProps]);
|
|
299
302
|
// Apply `triggerAriaLabel` to a `Tooltip.InlineTrigger` trigger (it has no `ariaLabel` prop
|
|
300
303
|
// of its own); the default info-icon trigger below handles `triggerAriaLabel` separately.
|
|
301
304
|
// An `aria-label` set directly on the `Tooltip.InlineTrigger` element takes priority over
|
|
@@ -322,7 +325,6 @@ const Tooltip = ({
|
|
|
322
325
|
isTooltipVisible: isTooltipVisible,
|
|
323
326
|
onKeyDownHandler: onKeyDownHandler,
|
|
324
327
|
handleUniversalClick: handleUniversalClick,
|
|
325
|
-
isTriggerDisabledFocusable: isTriggerDisabledFocusable,
|
|
326
328
|
setTooltipVisible: setTooltipVisible,
|
|
327
329
|
timerRef: timerRef,
|
|
328
330
|
timerMouseLeaveRef: timerMouseLeaveRef,
|
package/esm/button/button.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
|
|
|
9
9
|
title?: string;
|
|
10
10
|
type?: TButton;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
-
focusableWhenDisabled?: boolean;
|
|
13
12
|
onClick?: React.MouseEventHandler;
|
|
14
13
|
onBlur?: React.FocusEventHandler;
|
|
15
14
|
onMouseOver?: (e: React.MouseEvent<HTMLElement>) => void;
|
|
@@ -28,6 +27,6 @@ export interface IButton extends IZenComponentProps, IZenGridItem {
|
|
|
28
27
|
dataAnalyticsId?: string;
|
|
29
28
|
}
|
|
30
29
|
export declare const Button: {
|
|
31
|
-
({ className, type, disabled,
|
|
30
|
+
({ className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId, ...other }: IButton): import("react/jsx-runtime").JSX.Element;
|
|
32
31
|
displayName: string;
|
|
33
32
|
};
|
package/esm/button/button.js
CHANGED
|
@@ -15,35 +15,18 @@ import { classNames } from "../commonHelpers/classNames/classNames";
|
|
|
15
15
|
import { useDriveClassName } from "../utils/theme/useDriveClassName";
|
|
16
16
|
import { filterGridProps } from "../gridLayout/utils/filterGridProps";
|
|
17
17
|
import { useMobileClassName } from "../utils/theme/useMobileClassName";
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
event.stopPropagation();
|
|
21
|
-
};
|
|
22
|
-
// Guards on `disabled` alone, not just `focusableWhenDisabled`, so clicks are blocked in JS regardless of
|
|
23
|
-
// CSS. `onKeyDown`/`onKeyUp` stay unguarded — Tooltip needs them.
|
|
24
|
-
const getDisabledGuardedHandlers = (disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp) => ({
|
|
25
|
-
onClick: disabled ? blockActivation : onClick,
|
|
26
|
-
onMouseDown: isFocusableWhenDisabled ? blockActivation : onMouseDown,
|
|
27
|
-
onMouseUp: isFocusableWhenDisabled ? blockActivation : onMouseUp
|
|
28
|
-
});
|
|
29
|
-
const getButtonClassName = (type, disabled, isFocusableWhenDisabled, mobileOrDriveClasses, className) => {
|
|
18
|
+
export const Button = (_a) => {
|
|
19
|
+
var { className, type, disabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
|
|
30
20
|
const modifierClassName = type && type !== ButtonType.Secondary ? `zen-button--${type}` : "";
|
|
31
21
|
const disabledClassName = disabled ? "zen-button--disabled" : "";
|
|
32
|
-
const disabledFocusableClassName = isFocusableWhenDisabled ? "zen-button--disabled-focusable" : "";
|
|
33
|
-
return classNames(["zen-button", mobileOrDriveClasses, modifierClassName, disabledClassName, disabledFocusableClassName, className !== null && className !== void 0 ? className : ""]);
|
|
34
|
-
};
|
|
35
|
-
export const Button = (_a) => {
|
|
36
|
-
var { className, type, disabled, focusableWhenDisabled, id, title, onClick, onMouseOver, onMouseOut, onMouseDown, onMouseUp, onKeyDown, onKeyUp, children, link, target, htmlType, rel, ref, name, onBlur, role, ariaLabel, dataAnalyticsId } = _a, other = __rest(_a, ["className", "type", "disabled", "focusableWhenDisabled", "id", "title", "onClick", "onMouseOver", "onMouseOut", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "children", "link", "target", "htmlType", "rel", "ref", "name", "onBlur", "role", "ariaLabel", "dataAnalyticsId"]);
|
|
37
|
-
const isFocusableWhenDisabled = Boolean(disabled) && Boolean(focusableWhenDisabled);
|
|
38
22
|
const driveClasses = useDriveClassName("zen-button");
|
|
39
23
|
const ariaLabelValue = ariaLabel || title;
|
|
40
24
|
const mobileClasses = useMobileClassName("zen-button");
|
|
41
25
|
const commonArgs = Object.assign(Object.assign(Object.assign({ id,
|
|
42
|
-
title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className:
|
|
43
|
-
const { onClick: onClickWithDisabledGuard, onMouseDown: onMouseDownWithDisabledGuard, onMouseUp: onMouseUpWithDisabledGuard } = getDisabledGuardedHandlers(disabled, isFocusableWhenDisabled, onClick, onMouseDown, onMouseUp);
|
|
26
|
+
title }, (ariaLabelValue ? { "aria-label": ariaLabelValue } : {})), (dataAnalyticsId ? { "data-analytics-id": dataAnalyticsId } : {})), { className: classNames(["zen-button", mobileClasses || driveClasses || "", modifierClassName, disabledClassName, className !== null && className !== void 0 ? className : ""]) });
|
|
44
27
|
if (link) {
|
|
45
|
-
return (_jsx("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link,
|
|
28
|
+
return (_jsx("a", Object.assign({ role: "link", rel: rel, target: target, href: disabled ? undefined : link, "aria-disabled": disabled, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onClick: onClick }, commonArgs, filterGridProps(other), { children: children })));
|
|
46
29
|
}
|
|
47
|
-
return (_jsx("button", Object.assign({ name: name, onBlur: onBlur, onClick:
|
|
30
|
+
return (_jsx("button", Object.assign({ name: name, onBlur: onBlur, onClick: onClick, onMouseOver: onMouseOver, onMouseOut: onMouseOut, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ref: ref, type: htmlType, disabled: disabled, role: role }, commonArgs, filterGridProps(other), { children: children })));
|
|
48
31
|
};
|
|
49
32
|
Button.displayName = "Button";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export declare const BUTTON_DISABLED_CLASS = "zen-button--disabled";
|
|
3
|
+
export declare const BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
|
|
4
|
+
export interface IButtonDisabledOptions {
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Keeps a disabled button in the tab order and hoverable, using `aria-disabled` instead of the native
|
|
8
|
+
* `disabled` attribute. Has no effect while the button is enabled.
|
|
9
|
+
*/
|
|
10
|
+
focusable?: boolean;
|
|
11
|
+
/** The `link` prop of the button, a link without `href` is not tabbable, so it needs an explicit `tabIndex`. */
|
|
12
|
+
link?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IButtonDisabledProps {
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
"aria-disabled"?: boolean;
|
|
17
|
+
tabIndex?: number;
|
|
18
|
+
className?: string;
|
|
19
|
+
onClick?: (event: React.SyntheticEvent) => void;
|
|
20
|
+
onMouseDown?: (event: React.SyntheticEvent) => void;
|
|
21
|
+
onMouseUp?: (event: React.SyntheticEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
|
|
25
|
+
*
|
|
26
|
+
* - enabled: `{ disabled }`
|
|
27
|
+
* - disabled: native `disabled`, the button leaves the tab order
|
|
28
|
+
* - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
|
|
29
|
+
*
|
|
30
|
+
* `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
|
|
31
|
+
* `disabled` attribute fire a click, which is blocked here.
|
|
32
|
+
*
|
|
33
|
+
* The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
|
|
34
|
+
*/
|
|
35
|
+
export declare const getButtonDisabledProps: ({ disabled, focusable, link }: IButtonDisabledOptions) => IButtonDisabledProps;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
2
|
+
// `button.less` styles these classes: `--disabled-focusable` keeps pointer events so hover/tooltip still work.
|
|
3
|
+
export const BUTTON_DISABLED_CLASS = "zen-button--disabled";
|
|
4
|
+
export const BUTTON_DISABLED_FOCUSABLE_CLASS = "zen-button--disabled-focusable";
|
|
5
|
+
const blockActivation = (event) => {
|
|
6
|
+
event.preventDefault();
|
|
7
|
+
event.stopPropagation();
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Props that make `Button` (or `TextIconButton`) disabled. `Button` itself only applies them.
|
|
11
|
+
*
|
|
12
|
+
* - enabled: `{ disabled }`
|
|
13
|
+
* - disabled: native `disabled`, the button leaves the tab order
|
|
14
|
+
* - disabled and `focusable`: `aria-disabled`, activation is blocked, the button stays focusable and hoverable
|
|
15
|
+
*
|
|
16
|
+
* `onKeyDown`/`onKeyUp` are left untouched: Tooltip needs them. Enter/Space on a focused button without the native
|
|
17
|
+
* `disabled` attribute fire a click, which is blocked here.
|
|
18
|
+
*
|
|
19
|
+
* The caller merges the returned `className` with its own and wraps the button in whatever explains the state.
|
|
20
|
+
*/
|
|
21
|
+
export const getButtonDisabledProps = ({ disabled, focusable, link }) => {
|
|
22
|
+
if (!disabled || !focusable) {
|
|
23
|
+
return { disabled };
|
|
24
|
+
}
|
|
25
|
+
const hasLink = Boolean(link);
|
|
26
|
+
return Object.assign(Object.assign({}, (hasLink ? { disabled: true, tabIndex: 0 } : { "aria-disabled": true })), {
|
|
27
|
+
// Button adds `zen-button--disabled` to a disabled link itself.
|
|
28
|
+
className: classNames([hasLink ? "" : BUTTON_DISABLED_CLASS, BUTTON_DISABLED_FOCUSABLE_CLASS]), onClick: blockActivation, onMouseDown: blockActivation, onMouseUp: blockActivation });
|
|
29
|
+
};
|
|
@@ -2,6 +2,7 @@ import { IButton } from "../button/button";
|
|
|
2
2
|
import { TButtonIconPosition } from "../textIconButton/textIconButton";
|
|
3
3
|
import { TSvgIconSize } from "../svgIcon/svgIconSize";
|
|
4
4
|
import { IIcon } from "../icons/icon";
|
|
5
|
+
import { TAlignment } from "../absolute/absolute";
|
|
5
6
|
export interface IHeaderButton extends Omit<IButton, "onClick"> {
|
|
6
7
|
id: string;
|
|
7
8
|
important?: boolean;
|
|
@@ -11,10 +12,23 @@ export interface IHeaderButton extends Omit<IButton, "onClick"> {
|
|
|
11
12
|
iconPosition?: TButtonIconPosition;
|
|
12
13
|
iconClasses?: string;
|
|
13
14
|
iconSize?: TSvgIconSize;
|
|
15
|
+
/**
|
|
16
|
+
* Tooltip content shown only while the button is `disabled`.
|
|
17
|
+
*
|
|
18
|
+
* When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
|
|
19
|
+
* `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
|
|
20
|
+
* In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
|
|
21
|
+
* button is enabled.
|
|
22
|
+
*/
|
|
23
|
+
disabledTooltip?: React.ReactNode;
|
|
14
24
|
onClick?: (id: string, event: React.MouseEvent) => void;
|
|
15
25
|
}
|
|
26
|
+
interface IHeaderButtonInternal {
|
|
27
|
+
tooltipAlignment?: TAlignment;
|
|
28
|
+
}
|
|
16
29
|
export declare const HeaderButtonDisplayName = "HeaderButton";
|
|
17
30
|
export declare const HeaderButton: {
|
|
18
|
-
({ id, children, icon, iconOpen, iconPosition, className, important, onClick, inContextMenu, disabled,
|
|
31
|
+
({ id, children, icon, iconOpen, iconPosition, className, important, onClick, inContextMenu, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IHeaderButton & IHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
|
|
19
32
|
displayName: string;
|
|
20
33
|
};
|
|
34
|
+
export {};
|
|
@@ -13,13 +13,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
13
13
|
import { Button } from "../button/button";
|
|
14
14
|
import { ButtonIconPosition, TextIconButton } from "../textIconButton/textIconButton";
|
|
15
15
|
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
16
|
+
import { Tooltip } from "../tooltip/tooltip";
|
|
17
|
+
import { getButtonDisabledProps } from "../button/buttonDisabledProps";
|
|
16
18
|
export const HeaderButtonDisplayName = "HeaderButton";
|
|
17
19
|
export const HeaderButton = (_a) => {
|
|
18
|
-
var { id, children, icon, iconOpen, iconPosition = ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabled,
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
var { id, children, icon, iconOpen, iconPosition = ButtonIconPosition.Start, className = "", important, onClick, inContextMenu, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconOpen", "iconPosition", "className", "important", "onClick", "inContextMenu", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
|
|
21
|
+
const tooltip = disabled ? disabledTooltip : undefined;
|
|
22
|
+
// Native `disabled` takes a button out of the tab order and blocks hover, so a tooltip could never be reached.
|
|
23
|
+
// Only a disabled button that has a tooltip to show is kept focusable, via `aria-disabled`.
|
|
24
|
+
const _b = getButtonDisabledProps({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
|
|
25
|
+
const fullClassName = classNames(["zen-header-button", className, disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
|
|
26
|
+
const props = Object.assign(Object.assign(Object.assign({}, restProps), { link, "data-important": important ? "1" : "0" }), disabledProps);
|
|
27
|
+
const button = icon ? (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children }))) : (_jsx(Button, Object.assign({ id: id, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined }, props, { children: children })));
|
|
28
|
+
return tooltip ? (_jsx(Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
|
|
24
29
|
};
|
|
25
30
|
HeaderButton.displayName = HeaderButtonDisplayName;
|
|
@@ -10,60 +10,6 @@ import { useDrive } from "../../utils/theme/useDrive";
|
|
|
10
10
|
import { getMenuButtonState } from "../utils/getMenuButtonState";
|
|
11
11
|
import { isSafeHref } from "../utils/isSafeHref";
|
|
12
12
|
import { getSafeRel } from "../utils/getSafeRel";
|
|
13
|
-
// Templates below drop native `disabled` to stay focusable/hoverable; this guard blocks activation instead.
|
|
14
|
-
const handleDisabledGuardedClick = (e, disabled, id, onClick) => {
|
|
15
|
-
if (disabled) {
|
|
16
|
-
e.preventDefault();
|
|
17
|
-
e.stopPropagation();
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
21
|
-
};
|
|
22
|
-
// Skip Space-to-activate when disabled, so it doesn't flash `--active` or self-click via `linkEl.click()`.
|
|
23
|
-
const createDisabledGuardedLinkKeyEvents = (disabled) => ({
|
|
24
|
-
onKeyDown: (e) => {
|
|
25
|
-
if (disabled || e.key !== " ") {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
e.preventDefault();
|
|
29
|
-
const linkEl = e.target;
|
|
30
|
-
linkEl.dataset.spaceDown = "1";
|
|
31
|
-
linkEl.classList.add("zen-menu-button__action--active");
|
|
32
|
-
},
|
|
33
|
-
onKeyUp: (e) => {
|
|
34
|
-
if (disabled || e.key !== " ") {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
const linkEl = e.target;
|
|
39
|
-
linkEl.classList.remove("zen-menu-button__action--active");
|
|
40
|
-
if (linkEl.dataset.spaceDown) {
|
|
41
|
-
delete linkEl.dataset.spaceDown;
|
|
42
|
-
linkEl.click();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
const getMenuButtonActionClassName = (hasChildren, hasState, isActive, disabled, destructiveClass, driveClasses) => classNames([
|
|
47
|
-
"zen-menu-button__action",
|
|
48
|
-
"zen-button",
|
|
49
|
-
"zen-caption",
|
|
50
|
-
hasChildren ? "zen-menu-button__action--has-children" : "",
|
|
51
|
-
hasState && isActive ? "zen-menu-button__action--active" : "",
|
|
52
|
-
disabled ? "zen-menu-button__action--disabled" : "",
|
|
53
|
-
disabled ? "zen-button--disabled" : "",
|
|
54
|
-
disabled ? "zen-button--disabled-focusable" : "",
|
|
55
|
-
destructiveClass,
|
|
56
|
-
driveClasses || ""
|
|
57
|
-
]);
|
|
58
|
-
const getMenuLinkActionClassName = (disabled, destructiveClass, driveClasses) => classNames(["zen-menu-button__action", "zen-caption", disabled ? "zen-menu-button__action--disabled" : "", destructiveClass, driveClasses || ""]);
|
|
59
|
-
// An <a> without `href` isn't tabbable, so `tabIndex="0"` keeps a disabled link item in the tab order.
|
|
60
|
-
const getMenuLinkAccessibilityProps = (disabled, link) => ({
|
|
61
|
-
href: disabled || !isSafeHref(link) ? undefined : link,
|
|
62
|
-
tabIndex: disabled ? 0 : undefined,
|
|
63
|
-
"aria-disabled": disabled
|
|
64
|
-
});
|
|
65
|
-
const getMenuItemRole = (hasState) => (hasState ? "menuitemcheckbox" : "menuitem");
|
|
66
|
-
const getMenuItemAriaChecked = (hasState, isActive) => (hasState ? isActive : undefined);
|
|
67
13
|
const createMenuIcon = (iconComponent, size, className) => iconComponent ? createElement(iconComponent, { size, className }) : null;
|
|
68
14
|
const resolveTarget = (target, iconOpen) => target !== null && target !== void 0 ? target : (iconOpen ? "_blank" : undefined);
|
|
69
15
|
export const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen, destructive, name, link, target, rel, className = "", active = null, ref, dataAnalyticsId }) => {
|
|
@@ -74,10 +20,46 @@ export const MenuButton = ({ id, onClick, hasChildren, disabled, icon, iconOpen,
|
|
|
74
20
|
const destructiveClass = destructive ? "zen-menu-button__action--destructive" : "";
|
|
75
21
|
const effectiveTarget = resolveTarget(target, iconOpen);
|
|
76
22
|
const ChevronIcon = useDirectionalIcon(IconChevronRight, IconChevronLeft);
|
|
77
|
-
const linkEvents = useMemo(() =>
|
|
78
|
-
|
|
23
|
+
const linkEvents = useMemo(() => ({
|
|
24
|
+
onKeyDown: e => {
|
|
25
|
+
if (e.key === " ") {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const linkEl = e.target;
|
|
28
|
+
linkEl.dataset.spaceDown = "1";
|
|
29
|
+
linkEl.classList.add("zen-menu-button__action--active");
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
onKeyUp: e => {
|
|
33
|
+
if (e.key === " ") {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
const linkEl = e.target;
|
|
36
|
+
linkEl.classList.remove("zen-menu-button__action--active");
|
|
37
|
+
if (linkEl.dataset.spaceDown) {
|
|
38
|
+
delete linkEl.dataset.spaceDown;
|
|
39
|
+
linkEl.click();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}), []);
|
|
44
|
+
const onClickHandler = useCallback((e) => {
|
|
45
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(id, e);
|
|
46
|
+
}, [id, onClick]);
|
|
79
47
|
const iconAfterElement = createMenuIcon(iconOpen ? IconOpen : undefined, iconSize, "zen-menu-button__icon-after");
|
|
80
|
-
const buttonTemplate = (_jsxs("button", { type: "button", "data-id": id, ref: ref,
|
|
81
|
-
|
|
48
|
+
const buttonTemplate = (_jsxs("button", { type: "button", "data-id": id, ref: ref, disabled: !!disabled, title: name, className: classNames([
|
|
49
|
+
"zen-menu-button__action",
|
|
50
|
+
"zen-button",
|
|
51
|
+
"zen-caption",
|
|
52
|
+
hasChildren ? "zen-menu-button__action--has-children" : "",
|
|
53
|
+
hasState && isActive ? "zen-menu-button__action--active" : "",
|
|
54
|
+
destructiveClass,
|
|
55
|
+
driveMenuButtonActionClasses || ""
|
|
56
|
+
]), role: hasState ? "menuitemcheckbox" : "menuitem", onClick: onClickHandler, "aria-checked": hasState ? isActive : undefined, "data-analytics-id": dataAnalyticsId, children: [createMenuIcon(icon, iconSize, classNames(["zen-caption__pre-content", "zen-menu-button__icon"])), _jsx("span", { className: "zen-menu-button__action-text", children: name }), iconAfterElement, !!hasChildren && _jsx(ChevronIcon, { size: iconSize, className: classNames(["zen-caption__pre-content", "zen-menu-button__more-icon"]) })] }));
|
|
57
|
+
const linkTemplate = (_jsxs("a", Object.assign({ "data-id": id, role: "menuitem", className: classNames([
|
|
58
|
+
"zen-menu-button__action",
|
|
59
|
+
"zen-caption",
|
|
60
|
+
disabled ? "zen-menu-button__action--disabled" : "",
|
|
61
|
+
destructiveClass,
|
|
62
|
+
driveMenuButtonActionClasses || ""
|
|
63
|
+
]), href: disabled || !isSafeHref(link) ? undefined : link, "aria-disabled": disabled, onClick: onClickHandler, target: effectiveTarget, rel: getSafeRel(rel, effectiveTarget), "data-analytics-id": dataAnalyticsId }, linkEvents, { children: [createMenuIcon(icon, iconSize, "zen-caption__pre-content"), iconAfterElement ? _jsx("span", { className: "zen-menu-button__action-text", children: name }) : name, iconAfterElement] })));
|
|
82
64
|
return (_jsx("li", { role: "presentation", className: classNames(["zen-menu-button", className]), children: link ? linkTemplate : buttonTemplate }));
|
|
83
65
|
};
|
|
@@ -2,6 +2,7 @@ import { IButton } from "../button/button";
|
|
|
2
2
|
import { TButtonIconPosition } from "../textIconButton/textIconButton";
|
|
3
3
|
import { TSvgIconSize } from "../svgIcon/svgIconSize";
|
|
4
4
|
import { IIcon } from "../icons/icon";
|
|
5
|
+
import { TAlignment } from "../absolute/absolute";
|
|
5
6
|
export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "title"> {
|
|
6
7
|
id: string;
|
|
7
8
|
icon: React.FC<IIcon>;
|
|
@@ -9,10 +10,23 @@ export interface IPageHeaderButton extends Omit<IButton, "onClick" | "icon" | "t
|
|
|
9
10
|
iconPosition?: TButtonIconPosition;
|
|
10
11
|
iconClasses?: string;
|
|
11
12
|
iconSize?: TSvgIconSize;
|
|
13
|
+
/**
|
|
14
|
+
* Tooltip content shown only while the button is `disabled`.
|
|
15
|
+
*
|
|
16
|
+
* When `disabled` and `disabledTooltip` are both set, the button uses `aria-disabled` instead of the native
|
|
17
|
+
* `disabled` attribute, so it stays focusable and hoverable and the tooltip can explain why it is unavailable.
|
|
18
|
+
* In every other case the standard `disabled` attribute is used, and `disabledTooltip` is ignored while the
|
|
19
|
+
* button is enabled.
|
|
20
|
+
*/
|
|
21
|
+
disabledTooltip?: React.ReactNode;
|
|
12
22
|
onClick?: (id: string, event: React.MouseEvent) => void;
|
|
13
23
|
}
|
|
24
|
+
interface IPageHeaderButtonInternal {
|
|
25
|
+
tooltipAlignment?: TAlignment;
|
|
26
|
+
}
|
|
14
27
|
export declare const PageHeaderButtonDisplayName = "PageHeaderButton";
|
|
15
28
|
export declare const PageHeaderButton: {
|
|
16
|
-
({ id, children, icon, iconPosition, className, onClick, title, disabled,
|
|
29
|
+
({ id, children, icon, iconPosition, className, onClick, title, disabledTooltip, tooltipAlignment, disabled, link, ...restProps }: IPageHeaderButton & IPageHeaderButtonInternal): import("react/jsx-runtime").JSX.Element;
|
|
17
30
|
displayName: string;
|
|
18
31
|
};
|
|
32
|
+
export {};
|
|
@@ -12,13 +12,15 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
13
|
import { ButtonIconPosition, TextIconButton } from "../textIconButton/textIconButton";
|
|
14
14
|
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
15
|
+
import { Tooltip } from "../tooltip/tooltip";
|
|
16
|
+
import { getButtonDisabledProps } from "../button/buttonDisabledProps";
|
|
15
17
|
export const PageHeaderButtonDisplayName = "PageHeaderButton";
|
|
16
18
|
export const PageHeaderButton = (_a) => {
|
|
17
|
-
var { id, children, icon, iconPosition = ButtonIconPosition.Start, className = "", onClick, title, disabled,
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return (_jsx(
|
|
19
|
+
var { id, children, icon, iconPosition = ButtonIconPosition.Start, className = "", onClick, title, disabledTooltip, tooltipAlignment = "bottom", disabled, link } = _a, restProps = __rest(_a, ["id", "children", "icon", "iconPosition", "className", "onClick", "title", "disabledTooltip", "tooltipAlignment", "disabled", "link"]);
|
|
20
|
+
const tooltip = disabled ? disabledTooltip : undefined;
|
|
21
|
+
const _b = getButtonDisabledProps({ disabled, focusable: Boolean(tooltip), link }), { className: disabledClassName } = _b, disabledProps = __rest(_b, ["className"]);
|
|
22
|
+
const fullClassName = classNames(["zen-page-header-button", className || "", disabledClassName !== null && disabledClassName !== void 0 ? disabledClassName : ""]);
|
|
23
|
+
const button = (_jsx(TextIconButton, Object.assign({ id: id, icon: icon, title: title, iconPosition: iconPosition, className: fullClassName, onClick: onClick ? e => onClick(id, e) : undefined, link: link }, restProps, disabledProps, { children: children })));
|
|
24
|
+
return tooltip ? (_jsx(Tooltip, { trigger: button, alignment: tooltipAlignment, children: tooltip })) : (button);
|
|
23
25
|
};
|
|
24
26
|
PageHeaderButton.displayName = PageHeaderButtonDisplayName;
|
package/esm/tooltip/tooltip.d.ts
CHANGED
|
@@ -5,11 +5,13 @@ import { TAlignment } from "../absolute/absolute";
|
|
|
5
5
|
import { IInlineTrigger } from "./components/inlineTrigger";
|
|
6
6
|
export type TooltipSize = "large" | "small" | "auto";
|
|
7
7
|
export type TMobileView = "default" | "mobileSheet";
|
|
8
|
+
interface ITriggerDisabledProps {
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
"aria-disabled"?: boolean | "true" | "false";
|
|
11
|
+
}
|
|
8
12
|
interface ITooltip extends IZenComponentProps {
|
|
9
|
-
trigger?: ReactElement<PropsWithChildren & {
|
|
13
|
+
trigger?: ReactElement<PropsWithChildren & ITriggerDisabledProps & {
|
|
10
14
|
onClick?: (e: React.MouseEvent) => void;
|
|
11
|
-
disabled?: boolean;
|
|
12
|
-
focusableWhenDisabled?: boolean;
|
|
13
15
|
}>;
|
|
14
16
|
alignment?: TAlignment;
|
|
15
17
|
triggerClassName?: string;
|
package/esm/tooltip/tooltip.js
CHANGED
|
@@ -110,12 +110,14 @@ const getInitialDeviceType = () => {
|
|
|
110
110
|
}
|
|
111
111
|
return DeviceType.Desktop;
|
|
112
112
|
};
|
|
113
|
+
// A disabled trigger (native `disabled`, or `aria-disabled` when it must stay focusable) never runs its own
|
|
114
|
+
// `onClick`, so the tooltip has to react to the click capture phase instead.
|
|
115
|
+
const isTriggerDisabled = props => Boolean(props === null || props === void 0 ? void 0 : props.disabled) || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === true || (props === null || props === void 0 ? void 0 : props["aria-disabled"]) === "true";
|
|
113
116
|
const EventWrapper = ({
|
|
114
117
|
children,
|
|
115
118
|
deviceType,
|
|
116
119
|
onKeyDownHandler,
|
|
117
120
|
handleUniversalClick,
|
|
118
|
-
isTriggerDisabledFocusable,
|
|
119
121
|
setTooltipVisible,
|
|
120
122
|
timerRef,
|
|
121
123
|
timerMouseLeaveRef,
|
|
@@ -124,8 +126,9 @@ const EventWrapper = ({
|
|
|
124
126
|
const isCurrentDeviceDesktop = deviceType === DeviceType.Desktop;
|
|
125
127
|
const triggerProps = children.props;
|
|
126
128
|
const originalOnClick = triggerProps.onClick;
|
|
127
|
-
// A disabled
|
|
128
|
-
//
|
|
129
|
+
// A disabled trigger doesn't run its own `onClick`, so on mobile the tap is handled in the capture phase
|
|
130
|
+
// instead — see `mobileEvents` below.
|
|
131
|
+
const isDisabledTrigger = isTriggerDisabled(triggerProps);
|
|
129
132
|
const composedOnClick = useCallback(e => {
|
|
130
133
|
// Call original onClick first if it exists
|
|
131
134
|
if (originalOnClick) {
|
|
@@ -164,7 +167,7 @@ const EventWrapper = ({
|
|
|
164
167
|
}, [handleUniversalClick]);
|
|
165
168
|
const mobileEvents = Object.assign({
|
|
166
169
|
onKeyDown: mobileOnKeyDown
|
|
167
|
-
},
|
|
170
|
+
}, isDisabledTrigger ? {
|
|
168
171
|
onClickCapture: handleUniversalClick
|
|
169
172
|
} : {
|
|
170
173
|
onClick: composedOnClick
|
|
@@ -199,7 +202,7 @@ export const Tooltip = ({
|
|
|
199
202
|
const [deviceType, setDeviceType] = useState(getInitialDeviceType);
|
|
200
203
|
const isMobile = deviceType === DeviceType.Mobile;
|
|
201
204
|
const triggerProps = trigger === null || trigger === void 0 ? void 0 : trigger.props;
|
|
202
|
-
const
|
|
205
|
+
const isDisabledTrigger = isTriggerDisabled(triggerProps);
|
|
203
206
|
const tooltipId = useId();
|
|
204
207
|
const {
|
|
205
208
|
translate
|
|
@@ -230,7 +233,7 @@ export const Tooltip = ({
|
|
|
230
233
|
}
|
|
231
234
|
};
|
|
232
235
|
const handleUniversalClick = useCallback(e => {
|
|
233
|
-
const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !
|
|
236
|
+
const hasExternalClickHandler = !!(triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.onClick) && !isDisabledTrigger;
|
|
234
237
|
if (deviceType === DeviceType.Desktop) {
|
|
235
238
|
setTooltipVisible(currentVisible => {
|
|
236
239
|
if (currentVisible) {
|
|
@@ -239,14 +242,14 @@ export const Tooltip = ({
|
|
|
239
242
|
return currentVisible;
|
|
240
243
|
});
|
|
241
244
|
} else {
|
|
242
|
-
if (!
|
|
245
|
+
if (!isDisabledTrigger) {
|
|
243
246
|
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
244
247
|
}
|
|
245
248
|
if (!hasExternalClickHandler) {
|
|
246
249
|
setTooltipVisible(isVisible => !isVisible);
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
|
-
}, [deviceType,
|
|
252
|
+
}, [deviceType, isDisabledTrigger, triggerProps]);
|
|
250
253
|
// Apply `triggerAriaLabel` to a `Tooltip.InlineTrigger` trigger (it has no `ariaLabel` prop
|
|
251
254
|
// of its own); the default info-icon trigger below handles `triggerAriaLabel` separately.
|
|
252
255
|
// An `aria-label` set directly on the `Tooltip.InlineTrigger` element takes priority over
|
|
@@ -273,7 +276,6 @@ export const Tooltip = ({
|
|
|
273
276
|
isTooltipVisible: isTooltipVisible,
|
|
274
277
|
onKeyDownHandler: onKeyDownHandler,
|
|
275
278
|
handleUniversalClick: handleUniversalClick,
|
|
276
|
-
isTriggerDisabledFocusable: isTriggerDisabledFocusable,
|
|
277
279
|
setTooltipVisible: setTooltipVisible,
|
|
278
280
|
timerRef: timerRef,
|
|
279
281
|
timerMouseLeaveRef: timerMouseLeaveRef,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geotab/zenith",
|
|
3
|
-
"version": "3.17.0
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Zenith components library on React",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "esm/index.d.ts",
|
|
@@ -156,5 +156,8 @@
|
|
|
156
156
|
"minimatch@<9": {
|
|
157
157
|
"brace-expansion": "^1.1.18"
|
|
158
158
|
}
|
|
159
|
+
},
|
|
160
|
+
"publishConfig": {
|
|
161
|
+
"access": "public"
|
|
159
162
|
}
|
|
160
163
|
}
|