@behivetech/atoms.tooltip 0.0.0-stage → 0.2.3
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 +45 -2
- package/dist/index.cjs +198 -0
- package/dist/index.css +39 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +184 -0
- package/docs.json +43 -0
- package/index.ts +2 -0
- package/package.json +59 -3
- package/tooltip.example.tsx +33 -0
- package/tooltip.module.scss +55 -0
- package/tooltip.tsx +87 -0
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tooltip
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Names a control on hover and on keyboard focus, built on
|
|
4
|
+
[`@radix-ui/react-tooltip`](https://www.radix-ui.com/primitives/docs/components/tooltip).
|
|
5
|
+
|
|
6
|
+
Intended for icon-only controls, where the tooltip is the only thing naming the
|
|
7
|
+
control. It replaces the `title` attribute, which delays about a second, is
|
|
8
|
+
OS-styled with no theming, has no touch support, and gives no control over
|
|
9
|
+
placement.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```jsx
|
|
14
|
+
import { Tooltip } from "@behivetech/atoms.tooltip";
|
|
15
|
+
import { BoxIcon } from "@behivetech/atoms.icon";
|
|
16
|
+
|
|
17
|
+
<Tooltip label="Rectangle" side="right">
|
|
18
|
+
<button type="button">
|
|
19
|
+
<BoxIcon />
|
|
20
|
+
</button>
|
|
21
|
+
</Tooltip>;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The child is rendered as-is via Radix's `asChild`, so the trigger stays your own
|
|
25
|
+
element. `Tooltip` mounts its own provider — you do not need to add one.
|
|
26
|
+
|
|
27
|
+
## Disabled triggers
|
|
28
|
+
|
|
29
|
+
A disabled element fires no pointer events, so a bare Radix tooltip never opens
|
|
30
|
+
on one. That is the moment a tooltip matters most: when a control is unavailable
|
|
31
|
+
and the user wants to know why.
|
|
32
|
+
|
|
33
|
+
`Tooltip` handles it. When the child is `disabled` it is wrapped in a focusable
|
|
34
|
+
span that carries the listeners, and the child is made pointer-transparent so
|
|
35
|
+
hover lands on the wrapper. The control stays disabled, keeps its own accessible
|
|
36
|
+
name, and the explanation is reachable by keyboard and screen reader.
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
<Tooltip label="You do not have permission to edit roads">
|
|
40
|
+
<button type="button" disabled>
|
|
41
|
+
<RoadIcon />
|
|
42
|
+
</button>
|
|
43
|
+
</Tooltip>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
No wrapper is added when the trigger is enabled.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
|
|
34
|
+
var require_classnames = __commonJS({
|
|
35
|
+
"../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports2, module2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
(function() {
|
|
38
|
+
"use strict";
|
|
39
|
+
var hasOwn = {}.hasOwnProperty;
|
|
40
|
+
function classNames() {
|
|
41
|
+
var classes = "";
|
|
42
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
43
|
+
var arg = arguments[i];
|
|
44
|
+
if (arg) {
|
|
45
|
+
classes = appendClass(classes, parseValue(arg));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return classes;
|
|
49
|
+
}
|
|
50
|
+
function parseValue(arg) {
|
|
51
|
+
if (typeof arg === "string" || typeof arg === "number") {
|
|
52
|
+
return arg;
|
|
53
|
+
}
|
|
54
|
+
if (typeof arg !== "object") {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(arg)) {
|
|
58
|
+
return classNames.apply(null, arg);
|
|
59
|
+
}
|
|
60
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
|
|
61
|
+
return arg.toString();
|
|
62
|
+
}
|
|
63
|
+
var classes = "";
|
|
64
|
+
for (var key in arg) {
|
|
65
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
66
|
+
classes = appendClass(classes, key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return classes;
|
|
70
|
+
}
|
|
71
|
+
function appendClass(value, newClass) {
|
|
72
|
+
if (!newClass) {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (value) {
|
|
76
|
+
return value + " " + newClass;
|
|
77
|
+
}
|
|
78
|
+
return value + newClass;
|
|
79
|
+
}
|
|
80
|
+
if (typeof module2 !== "undefined" && module2.exports) {
|
|
81
|
+
classNames.default = classNames;
|
|
82
|
+
module2.exports = classNames;
|
|
83
|
+
} else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
|
|
84
|
+
define("classnames", [], function() {
|
|
85
|
+
return classNames;
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
window.classNames = classNames;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// index.ts
|
|
95
|
+
var index_exports = {};
|
|
96
|
+
__export(index_exports, {
|
|
97
|
+
Tooltip: () => Tooltip
|
|
98
|
+
});
|
|
99
|
+
module.exports = __toCommonJS(index_exports);
|
|
100
|
+
|
|
101
|
+
// tooltip.tsx
|
|
102
|
+
var import_react = require("react");
|
|
103
|
+
var RadixTooltip = __toESM(require("@radix-ui/react-tooltip"), 1);
|
|
104
|
+
|
|
105
|
+
// ../../../../packages/registry/class-names/dist/index.js
|
|
106
|
+
var import_classnames = __toESM(require_classnames(), 1);
|
|
107
|
+
var getClassName = ({
|
|
108
|
+
className,
|
|
109
|
+
modifiers = {},
|
|
110
|
+
rootClass,
|
|
111
|
+
styles = {}
|
|
112
|
+
}) => {
|
|
113
|
+
function getModifiers() {
|
|
114
|
+
const updatedModifiers = {};
|
|
115
|
+
const modifierKeys = Object.keys(modifiers);
|
|
116
|
+
if (modifierKeys.length) {
|
|
117
|
+
modifierKeys.forEach((modKey) => {
|
|
118
|
+
const classKey = `${rootClass}--${modKey}`;
|
|
119
|
+
const newClassName = styles[classKey] || classKey;
|
|
120
|
+
updatedModifiers[newClassName] = modifiers[modKey] ?? false;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return updatedModifiers;
|
|
124
|
+
}
|
|
125
|
+
function getChildClass(childClassName) {
|
|
126
|
+
const classKey = `${rootClass}__${childClassName}`;
|
|
127
|
+
return styles[classKey] || classKey;
|
|
128
|
+
}
|
|
129
|
+
function getRootStyles() {
|
|
130
|
+
return (0, import_classnames.default)(
|
|
131
|
+
className,
|
|
132
|
+
{
|
|
133
|
+
...getModifiers()
|
|
134
|
+
},
|
|
135
|
+
styles[rootClass] || rootClass
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return [getRootStyles(), getChildClass];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// tooltip.module.scss
|
|
142
|
+
var tooltip_module_default = {
|
|
143
|
+
"BHT__Tooltip__content": "BHT__Tooltip__content",
|
|
144
|
+
"tooltip-in": "tooltip-in",
|
|
145
|
+
"BHT__Tooltip__arrow": "BHT__Tooltip__arrow",
|
|
146
|
+
"BHT__Tooltip__disabledTrigger": "BHT__Tooltip__disabledTrigger"
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// tooltip.tsx
|
|
150
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
151
|
+
var DELAY_DURATION = 300;
|
|
152
|
+
function isDisabled(element) {
|
|
153
|
+
if (!(0, import_react.isValidElement)(element)) return false;
|
|
154
|
+
const { disabled } = element.props;
|
|
155
|
+
return disabled === true;
|
|
156
|
+
}
|
|
157
|
+
var Tooltip = ({
|
|
158
|
+
label,
|
|
159
|
+
side = "top",
|
|
160
|
+
children,
|
|
161
|
+
className
|
|
162
|
+
}) => {
|
|
163
|
+
const [, getChildClass] = getClassName({
|
|
164
|
+
className,
|
|
165
|
+
rootClass: "BHT__Tooltip",
|
|
166
|
+
styles: tooltip_module_default
|
|
167
|
+
});
|
|
168
|
+
const disabled = isDisabled(children);
|
|
169
|
+
const trigger = disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("disabledTrigger"), tabIndex: 0, children }) : children;
|
|
170
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixTooltip.Provider, { delayDuration: DELAY_DURATION, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RadixTooltip.Root, { children: [
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixTooltip.Trigger, { asChild: true, children: trigger }),
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixTooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
173
|
+
RadixTooltip.Content,
|
|
174
|
+
{
|
|
175
|
+
className: getChildClass("content"),
|
|
176
|
+
side,
|
|
177
|
+
sideOffset: 6,
|
|
178
|
+
children: [
|
|
179
|
+
label,
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixTooltip.Arrow, { className: getChildClass("arrow") })
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
) })
|
|
184
|
+
] }) });
|
|
185
|
+
};
|
|
186
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
187
|
+
0 && (module.exports = {
|
|
188
|
+
Tooltip
|
|
189
|
+
});
|
|
190
|
+
/*! Bundled license information:
|
|
191
|
+
|
|
192
|
+
classnames/index.js:
|
|
193
|
+
(*!
|
|
194
|
+
Copyright (c) 2018 Jed Watson.
|
|
195
|
+
Licensed under the MIT License (MIT), see
|
|
196
|
+
http://jedwatson.github.io/classnames
|
|
197
|
+
*)
|
|
198
|
+
*/
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* esbuild-sass-plugin:css-chunktooltip.module.scss */
|
|
2
|
+
.BHT__Tooltip__content {
|
|
3
|
+
padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);
|
|
4
|
+
background: var(--md-sys-color-inverse-surface, var(--color-on-surface));
|
|
5
|
+
color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
|
|
6
|
+
border-radius: var(--md-sys-shape-corner-extra-small, 4px);
|
|
7
|
+
font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
|
|
8
|
+
font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
|
|
9
|
+
font-weight: var(--md-sys-typescale-body-small-weight, 400);
|
|
10
|
+
line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
|
|
11
|
+
letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
|
|
12
|
+
max-width: 16rem;
|
|
13
|
+
z-index: var(--z-tooltip, 600);
|
|
14
|
+
user-select: none;
|
|
15
|
+
}
|
|
16
|
+
.BHT__Tooltip__content[data-state=delayed-open] {
|
|
17
|
+
animation: tooltip-in var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard-decelerate, ease-out);
|
|
18
|
+
}
|
|
19
|
+
.BHT__Tooltip__arrow {
|
|
20
|
+
fill: var(--md-sys-color-inverse-surface, var(--color-on-surface));
|
|
21
|
+
}
|
|
22
|
+
.BHT__Tooltip__disabledTrigger {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
}
|
|
25
|
+
.BHT__Tooltip__disabledTrigger > * {
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
}
|
|
28
|
+
.BHT__Tooltip__disabledTrigger:focus-visible {
|
|
29
|
+
outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
|
|
30
|
+
outline-offset: 2px;
|
|
31
|
+
}
|
|
32
|
+
@keyframes tooltip-in {
|
|
33
|
+
from {
|
|
34
|
+
opacity: 0;
|
|
35
|
+
}
|
|
36
|
+
to {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
type TooltipSide = "top" | "right" | "bottom" | "left";
|
|
5
|
+
interface TooltipProps {
|
|
6
|
+
/** Text shown in the tooltip. This is the control's name when the trigger is icon-only. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Which side of the trigger to place the tooltip on; defaults to "top" */
|
|
9
|
+
side?: TooltipSide;
|
|
10
|
+
/** The element the tooltip describes — rendered as-is via Radix's `asChild` */
|
|
11
|
+
children: ReactElement;
|
|
12
|
+
/** Additional class names to merge with the tooltip content element */
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Names a control on hover and on keyboard focus, built on Radix's `Tooltip`.
|
|
17
|
+
*
|
|
18
|
+
* Mounts its own `Tooltip.Provider`, so a caller never has to add one.
|
|
19
|
+
*
|
|
20
|
+
* Works on a disabled trigger, which a bare Radix tooltip does not: a disabled
|
|
21
|
+
* element fires no pointer events, so the trigger never sees hover or focus.
|
|
22
|
+
* When the child is disabled it is wrapped in a focusable span that carries the
|
|
23
|
+
* listeners instead, and the child is made pointer-transparent so hover lands
|
|
24
|
+
* on the wrapper. That keeps the explanation reachable at the one moment it
|
|
25
|
+
* matters most — when the control is unavailable and the user wants to know
|
|
26
|
+
* why — including by keyboard, since a disabled control is not focusable.
|
|
27
|
+
*/
|
|
28
|
+
declare const Tooltip: ({ label, side, children, className, }: TooltipProps) => react.JSX.Element;
|
|
29
|
+
|
|
30
|
+
export { Tooltip, type TooltipProps, type TooltipSide };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
// ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
|
|
28
|
+
var require_classnames = __commonJS({
|
|
29
|
+
"../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports, module) {
|
|
30
|
+
"use strict";
|
|
31
|
+
(function() {
|
|
32
|
+
"use strict";
|
|
33
|
+
var hasOwn = {}.hasOwnProperty;
|
|
34
|
+
function classNames() {
|
|
35
|
+
var classes = "";
|
|
36
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
37
|
+
var arg = arguments[i];
|
|
38
|
+
if (arg) {
|
|
39
|
+
classes = appendClass(classes, parseValue(arg));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return classes;
|
|
43
|
+
}
|
|
44
|
+
function parseValue(arg) {
|
|
45
|
+
if (typeof arg === "string" || typeof arg === "number") {
|
|
46
|
+
return arg;
|
|
47
|
+
}
|
|
48
|
+
if (typeof arg !== "object") {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(arg)) {
|
|
52
|
+
return classNames.apply(null, arg);
|
|
53
|
+
}
|
|
54
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
|
|
55
|
+
return arg.toString();
|
|
56
|
+
}
|
|
57
|
+
var classes = "";
|
|
58
|
+
for (var key in arg) {
|
|
59
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
60
|
+
classes = appendClass(classes, key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return classes;
|
|
64
|
+
}
|
|
65
|
+
function appendClass(value, newClass) {
|
|
66
|
+
if (!newClass) {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
if (value) {
|
|
70
|
+
return value + " " + newClass;
|
|
71
|
+
}
|
|
72
|
+
return value + newClass;
|
|
73
|
+
}
|
|
74
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
75
|
+
classNames.default = classNames;
|
|
76
|
+
module.exports = classNames;
|
|
77
|
+
} else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
|
|
78
|
+
define("classnames", [], function() {
|
|
79
|
+
return classNames;
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
window.classNames = classNames;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// tooltip.tsx
|
|
89
|
+
import { isValidElement } from "react";
|
|
90
|
+
import * as RadixTooltip from "@radix-ui/react-tooltip";
|
|
91
|
+
|
|
92
|
+
// ../../../../packages/registry/class-names/dist/index.js
|
|
93
|
+
var import_classnames = __toESM(require_classnames(), 1);
|
|
94
|
+
var getClassName = ({
|
|
95
|
+
className,
|
|
96
|
+
modifiers = {},
|
|
97
|
+
rootClass,
|
|
98
|
+
styles = {}
|
|
99
|
+
}) => {
|
|
100
|
+
function getModifiers() {
|
|
101
|
+
const updatedModifiers = {};
|
|
102
|
+
const modifierKeys = Object.keys(modifiers);
|
|
103
|
+
if (modifierKeys.length) {
|
|
104
|
+
modifierKeys.forEach((modKey) => {
|
|
105
|
+
const classKey = `${rootClass}--${modKey}`;
|
|
106
|
+
const newClassName = styles[classKey] || classKey;
|
|
107
|
+
updatedModifiers[newClassName] = modifiers[modKey] ?? false;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return updatedModifiers;
|
|
111
|
+
}
|
|
112
|
+
function getChildClass(childClassName) {
|
|
113
|
+
const classKey = `${rootClass}__${childClassName}`;
|
|
114
|
+
return styles[classKey] || classKey;
|
|
115
|
+
}
|
|
116
|
+
function getRootStyles() {
|
|
117
|
+
return (0, import_classnames.default)(
|
|
118
|
+
className,
|
|
119
|
+
{
|
|
120
|
+
...getModifiers()
|
|
121
|
+
},
|
|
122
|
+
styles[rootClass] || rootClass
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return [getRootStyles(), getChildClass];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// tooltip.module.scss
|
|
129
|
+
var tooltip_module_default = {
|
|
130
|
+
"BHT__Tooltip__content": "BHT__Tooltip__content",
|
|
131
|
+
"tooltip-in": "tooltip-in",
|
|
132
|
+
"BHT__Tooltip__arrow": "BHT__Tooltip__arrow",
|
|
133
|
+
"BHT__Tooltip__disabledTrigger": "BHT__Tooltip__disabledTrigger"
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// tooltip.tsx
|
|
137
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
138
|
+
var DELAY_DURATION = 300;
|
|
139
|
+
function isDisabled(element) {
|
|
140
|
+
if (!isValidElement(element)) return false;
|
|
141
|
+
const { disabled } = element.props;
|
|
142
|
+
return disabled === true;
|
|
143
|
+
}
|
|
144
|
+
var Tooltip = ({
|
|
145
|
+
label,
|
|
146
|
+
side = "top",
|
|
147
|
+
children,
|
|
148
|
+
className
|
|
149
|
+
}) => {
|
|
150
|
+
const [, getChildClass] = getClassName({
|
|
151
|
+
className,
|
|
152
|
+
rootClass: "BHT__Tooltip",
|
|
153
|
+
styles: tooltip_module_default
|
|
154
|
+
});
|
|
155
|
+
const disabled = isDisabled(children);
|
|
156
|
+
const trigger = disabled ? /* @__PURE__ */ jsx("span", { className: getChildClass("disabledTrigger"), tabIndex: 0, children }) : children;
|
|
157
|
+
return /* @__PURE__ */ jsx(RadixTooltip.Provider, { delayDuration: DELAY_DURATION, children: /* @__PURE__ */ jsxs(RadixTooltip.Root, { children: [
|
|
158
|
+
/* @__PURE__ */ jsx(RadixTooltip.Trigger, { asChild: true, children: trigger }),
|
|
159
|
+
/* @__PURE__ */ jsx(RadixTooltip.Portal, { children: /* @__PURE__ */ jsxs(
|
|
160
|
+
RadixTooltip.Content,
|
|
161
|
+
{
|
|
162
|
+
className: getChildClass("content"),
|
|
163
|
+
side,
|
|
164
|
+
sideOffset: 6,
|
|
165
|
+
children: [
|
|
166
|
+
label,
|
|
167
|
+
/* @__PURE__ */ jsx(RadixTooltip.Arrow, { className: getChildClass("arrow") })
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
) })
|
|
171
|
+
] }) });
|
|
172
|
+
};
|
|
173
|
+
export {
|
|
174
|
+
Tooltip
|
|
175
|
+
};
|
|
176
|
+
/*! Bundled license information:
|
|
177
|
+
|
|
178
|
+
classnames/index.js:
|
|
179
|
+
(*!
|
|
180
|
+
Copyright (c) 2018 Jed Watson.
|
|
181
|
+
Licensed under the MIT License (MIT), see
|
|
182
|
+
http://jedwatson.github.io/classnames
|
|
183
|
+
*)
|
|
184
|
+
*/
|
package/docs.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
|
|
3
|
+
"version": 2,
|
|
4
|
+
"generatedAt": "2026-10-23T16:58:32.116Z",
|
|
5
|
+
"docs": [
|
|
6
|
+
{
|
|
7
|
+
"displayName": "Tooltip",
|
|
8
|
+
"description": "Names a control on hover and on keyboard focus, built on Radix's `Tooltip`.\n\nMounts its own `Tooltip.Provider`, so a caller never has to add one.\n\nWorks on a disabled trigger, which a bare Radix tooltip does not: a disabled\nelement fires no pointer events, so the trigger never sees hover or focus.\nWhen the child is disabled it is wrapped in a focusable span that carries the\nlisteners instead, and the child is made pointer-transparent so hover lands\non the wrapper. That keeps the explanation reachable at the one moment it\nmatters most — when the control is unavailable and the user wants to know\nwhy — including by keyboard, since a disabled control is not focusable.",
|
|
9
|
+
"extendsNative": false,
|
|
10
|
+
"props": [
|
|
11
|
+
{
|
|
12
|
+
"name": "children",
|
|
13
|
+
"type": "ReactElement<unknown, string | JSXElementConstructor<any>>",
|
|
14
|
+
"required": true,
|
|
15
|
+
"defaultValue": null,
|
|
16
|
+
"description": "The element the tooltip describes — rendered as-is via Radix's `asChild`"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "label",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"required": true,
|
|
22
|
+
"defaultValue": null,
|
|
23
|
+
"description": "Text shown in the tooltip. This is the control's name when the trigger is icon-only."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "className",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"required": false,
|
|
29
|
+
"defaultValue": null,
|
|
30
|
+
"description": "Additional class names to merge with the tooltip content element"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "side",
|
|
34
|
+
"type": "\"top\" | \"right\" | \"bottom\" | \"left\"",
|
|
35
|
+
"required": false,
|
|
36
|
+
"defaultValue": "top",
|
|
37
|
+
"description": "Which side of the trigger to place the tooltip on; defaults to \"top\""
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"functions": []
|
|
43
|
+
}
|
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@behivetech/atoms.tooltip",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"./styles.css": "./dist/index.css"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"index.ts",
|
|
19
|
+
"tooltip.tsx",
|
|
20
|
+
"tooltip.module.scss",
|
|
21
|
+
"*.example.tsx",
|
|
22
|
+
"docs.json"
|
|
23
|
+
],
|
|
24
|
+
"registry": {
|
|
25
|
+
"status": "stable",
|
|
26
|
+
"tags": []
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@radix-ui/react-tooltip": "^1.2.16"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^19.2.7",
|
|
33
|
+
"react-dom": "^19.2.7"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "19.2.17",
|
|
37
|
+
"@types/react-dom": "19.2.3",
|
|
38
|
+
"eslint": "^9.39.5",
|
|
39
|
+
"tsup": "^8.5.1",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.10",
|
|
42
|
+
"@bht-component-registry/class-names": "0.0.6",
|
|
43
|
+
"@bht-component-registry/env": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/behivetech/bht-galaxy.git",
|
|
48
|
+
"directory": "registry/behivetech/atoms/tooltip"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"registry": "https://registry.npmjs.org",
|
|
52
|
+
"access": "public",
|
|
53
|
+
"provenance": true
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup && bht-registry-docgen",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"lint": "eslint . --max-warnings 0",
|
|
59
|
+
"check-types": "tsc --noEmit",
|
|
60
|
+
"test": "vitest run"
|
|
61
|
+
}
|
|
6
62
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Tooltip } from "./tooltip.js";
|
|
2
|
+
|
|
3
|
+
export const BasicTooltip = () => (
|
|
4
|
+
<Tooltip label="Rectangle">
|
|
5
|
+
<button type="button">Rect</button>
|
|
6
|
+
</Tooltip>
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export const TooltipSides = () => (
|
|
10
|
+
<div style={{ display: "flex", gap: "1.5rem", padding: "3rem" }}>
|
|
11
|
+
<Tooltip label="Above" side="top">
|
|
12
|
+
<button type="button">Top</button>
|
|
13
|
+
</Tooltip>
|
|
14
|
+
<Tooltip label="To the right" side="right">
|
|
15
|
+
<button type="button">Right</button>
|
|
16
|
+
</Tooltip>
|
|
17
|
+
<Tooltip label="Below" side="bottom">
|
|
18
|
+
<button type="button">Bottom</button>
|
|
19
|
+
</Tooltip>
|
|
20
|
+
<Tooltip label="To the left" side="left">
|
|
21
|
+
<button type="button">Left</button>
|
|
22
|
+
</Tooltip>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/** The case a bare Radix tooltip cannot do — and the reason this is an atom. */
|
|
27
|
+
export const DisabledTrigger = () => (
|
|
28
|
+
<Tooltip label="You do not have permission to edit roads" side="right">
|
|
29
|
+
<button type="button" disabled>
|
|
30
|
+
Road
|
|
31
|
+
</button>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Material Design 3 plain tooltip. `inverse-surface` has no pre-MD3 name, so
|
|
2
|
+
// its fallback is the old on-surface/surface pair swapped — which is exactly
|
|
3
|
+
// what "inverse" means.
|
|
4
|
+
|
|
5
|
+
.BHT__Tooltip {
|
|
6
|
+
&__content {
|
|
7
|
+
padding: var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem);
|
|
8
|
+
background: var(--md-sys-color-inverse-surface, var(--color-on-surface));
|
|
9
|
+
color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
|
|
10
|
+
border-radius: var(--md-sys-shape-corner-extra-small, 4px);
|
|
11
|
+
font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
|
|
12
|
+
font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
|
|
13
|
+
font-weight: var(--md-sys-typescale-body-small-weight, 400);
|
|
14
|
+
line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
|
|
15
|
+
letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
|
|
16
|
+
max-width: 16rem;
|
|
17
|
+
z-index: var(--z-tooltip, 600);
|
|
18
|
+
user-select: none;
|
|
19
|
+
|
|
20
|
+
&[data-state="delayed-open"] {
|
|
21
|
+
animation: tooltip-in var(--md-sys-motion-duration-short-3, 150ms)
|
|
22
|
+
var(--md-sys-motion-easing-standard-decelerate, ease-out);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&__arrow {
|
|
27
|
+
fill: var(--md-sys-color-inverse-surface, var(--color-on-surface));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A disabled control fires no pointer events of its own, so the wrapper takes
|
|
31
|
+
// the listeners and the control is made transparent to the pointer.
|
|
32
|
+
&__disabledTrigger {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
|
|
35
|
+
> * {
|
|
36
|
+
pointer-events: none;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:focus-visible {
|
|
40
|
+
outline: 3px solid
|
|
41
|
+
var(--md-sys-color-secondary, var(--color-border-focus));
|
|
42
|
+
outline-offset: 2px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes tooltip-in {
|
|
48
|
+
from {
|
|
49
|
+
opacity: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
to {
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/tooltip.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isValidElement, type ReactElement } from "react";
|
|
2
|
+
import * as RadixTooltip from "@radix-ui/react-tooltip";
|
|
3
|
+
import { getClassName } from "@bht-component-registry/class-names";
|
|
4
|
+
import styles from "./tooltip.module.scss";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared open delay. Lives here rather than on each caller so tooltips feel the
|
|
8
|
+
* same everywhere — the `title` attribute's roughly-one-second delay is one of
|
|
9
|
+
* the reasons this component exists.
|
|
10
|
+
*/
|
|
11
|
+
const DELAY_DURATION = 300;
|
|
12
|
+
|
|
13
|
+
export type TooltipSide = "top" | "right" | "bottom" | "left";
|
|
14
|
+
|
|
15
|
+
export interface TooltipProps {
|
|
16
|
+
/** Text shown in the tooltip. This is the control's name when the trigger is icon-only. */
|
|
17
|
+
label: string;
|
|
18
|
+
/** Which side of the trigger to place the tooltip on; defaults to "top" */
|
|
19
|
+
side?: TooltipSide;
|
|
20
|
+
/** The element the tooltip describes — rendered as-is via Radix's `asChild` */
|
|
21
|
+
children: ReactElement;
|
|
22
|
+
/** Additional class names to merge with the tooltip content element */
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isDisabled(element: ReactElement): boolean {
|
|
27
|
+
if (!isValidElement(element)) return false;
|
|
28
|
+
const { disabled } = element.props as { disabled?: boolean };
|
|
29
|
+
return disabled === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Names a control on hover and on keyboard focus, built on Radix's `Tooltip`.
|
|
34
|
+
*
|
|
35
|
+
* Mounts its own `Tooltip.Provider`, so a caller never has to add one.
|
|
36
|
+
*
|
|
37
|
+
* Works on a disabled trigger, which a bare Radix tooltip does not: a disabled
|
|
38
|
+
* element fires no pointer events, so the trigger never sees hover or focus.
|
|
39
|
+
* When the child is disabled it is wrapped in a focusable span that carries the
|
|
40
|
+
* listeners instead, and the child is made pointer-transparent so hover lands
|
|
41
|
+
* on the wrapper. That keeps the explanation reachable at the one moment it
|
|
42
|
+
* matters most — when the control is unavailable and the user wants to know
|
|
43
|
+
* why — including by keyboard, since a disabled control is not focusable.
|
|
44
|
+
*/
|
|
45
|
+
export const Tooltip = ({
|
|
46
|
+
label,
|
|
47
|
+
side = "top",
|
|
48
|
+
children,
|
|
49
|
+
className,
|
|
50
|
+
}: TooltipProps) => {
|
|
51
|
+
const [, getChildClass] = getClassName({
|
|
52
|
+
className,
|
|
53
|
+
rootClass: "BHT__Tooltip",
|
|
54
|
+
styles,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const disabled = isDisabled(children);
|
|
58
|
+
|
|
59
|
+
// The wrapper is deliberately left unlabelled: the disabled control inside
|
|
60
|
+
// keeps its own accessible name, and Radix points the wrapper's
|
|
61
|
+
// `aria-describedby` at the tooltip, so focusing it announces both.
|
|
62
|
+
const trigger = disabled ? (
|
|
63
|
+
<span className={getChildClass("disabledTrigger")} tabIndex={0}>
|
|
64
|
+
{children}
|
|
65
|
+
</span>
|
|
66
|
+
) : (
|
|
67
|
+
children
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<RadixTooltip.Provider delayDuration={DELAY_DURATION}>
|
|
72
|
+
<RadixTooltip.Root>
|
|
73
|
+
<RadixTooltip.Trigger asChild>{trigger}</RadixTooltip.Trigger>
|
|
74
|
+
<RadixTooltip.Portal>
|
|
75
|
+
<RadixTooltip.Content
|
|
76
|
+
className={getChildClass("content")}
|
|
77
|
+
side={side}
|
|
78
|
+
sideOffset={6}
|
|
79
|
+
>
|
|
80
|
+
{label}
|
|
81
|
+
<RadixTooltip.Arrow className={getChildClass("arrow")} />
|
|
82
|
+
</RadixTooltip.Content>
|
|
83
|
+
</RadixTooltip.Portal>
|
|
84
|
+
</RadixTooltip.Root>
|
|
85
|
+
</RadixTooltip.Provider>
|
|
86
|
+
);
|
|
87
|
+
};
|