@beautinique/frontend-components 1.0.2 → 1.0.4
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 +48 -1
- package/dist/lib/button.d.cts +4 -1
- package/dist/lib/button.d.cts.map +1 -1
- package/dist/lib/button.d.ts +4 -1
- package/dist/lib/button.d.ts.map +1 -1
- package/dist/lib/constants.d.cts +4 -0
- package/dist/lib/constants.d.cts.map +1 -1
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/tooltip.d.cts +36 -4
- package/dist/lib/tooltip.d.cts.map +1 -1
- package/dist/lib/tooltip.d.ts +36 -4
- package/dist/lib/tooltip.d.ts.map +1 -1
- package/dist/toast/ToastContainer.d.cts +1 -1
- package/dist/toast/ToastContainer.d.cts.map +1 -1
- package/dist/toast/ToastContainer.d.ts +1 -1
- package/dist/toast/ToastContainer.d.ts.map +1 -1
- package/dist/toast/index.cjs +36 -43
- package/dist/toast/index.cjs.map +1 -1
- package/dist/toast/index.js +23 -30
- package/dist/toast/index.js.map +1 -1
- package/dist/toast/types.d.cts +2 -2
- package/dist/toast/types.d.cts.map +1 -1
- package/dist/toast/types.d.ts +2 -2
- package/dist/toast/types.d.ts.map +1 -1
- package/dist/types/component.d.cts +22 -4
- package/dist/types/component.d.cts.map +1 -1
- package/dist/types/component.d.ts +22 -4
- package/dist/types/component.d.ts.map +1 -1
- package/dist/ui/Button.d.cts +1 -1
- package/dist/ui/Button.d.cts.map +1 -1
- package/dist/ui/Button.d.ts +1 -1
- package/dist/ui/Button.d.ts.map +1 -1
- package/dist/ui/Tooltip.d.cts +1 -1
- package/dist/ui/Tooltip.d.cts.map +1 -1
- package/dist/ui/Tooltip.d.ts +1 -1
- package/dist/ui/Tooltip.d.ts.map +1 -1
- package/dist/ui/index.cjs +143 -62
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +143 -62
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -1,59 +1,53 @@
|
|
|
1
1
|
import { Icon } from '@iconify/react';
|
|
2
|
-
import { useRef, useState, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
3
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId, useRef, useState, useCallback, useEffect, useLayoutEffect, useMemo, isValidElement, cloneElement } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
7
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
8
|
|
|
9
9
|
// src/lib/button.ts
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return "text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover";
|
|
20
|
-
case "transparent":
|
|
21
|
-
default:
|
|
22
|
-
return "bg-transparent border border-primary/30 text-secondary";
|
|
23
|
-
}
|
|
24
|
-
}, "getButtonCss");
|
|
10
|
+
var PATTERN_CSS = {
|
|
11
|
+
primary: "text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover",
|
|
12
|
+
secondary: "text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover",
|
|
13
|
+
tertiary: "text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover",
|
|
14
|
+
outline: "text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover",
|
|
15
|
+
transparent: "bg-transparent border border-primary/30 text-secondary"
|
|
16
|
+
};
|
|
17
|
+
var getButtonCss = /* @__PURE__ */ __name((pattern) => Object.hasOwn(PATTERN_CSS, pattern) ? PATTERN_CSS[pattern] : PATTERN_CSS.transparent, "getButtonCss");
|
|
18
|
+
var BASE_CSS = "group flex w-full cursor-pointer items-center justify-center gap-1 rounded-lg px-4 py-3 text-sm leading-4 transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-85 lg:px-5 lg:py-4 xl:text-base";
|
|
25
19
|
var Button = /* @__PURE__ */ __name(({
|
|
26
20
|
pattern,
|
|
27
21
|
content,
|
|
28
22
|
className = "",
|
|
29
23
|
leftIcon,
|
|
30
24
|
rightIcon,
|
|
31
|
-
buttonProps
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}, "handleClick");
|
|
38
|
-
return /* @__PURE__ */ jsxs(
|
|
25
|
+
buttonProps,
|
|
26
|
+
ref
|
|
27
|
+
}) => (
|
|
28
|
+
// The classes that are given come last but do not remove the button's own: to win over one of
|
|
29
|
+
// them give it with `!` (`rounded-md!`), the way the toast's button does.
|
|
30
|
+
/* @__PURE__ */ jsxs(
|
|
39
31
|
"button",
|
|
40
32
|
{
|
|
41
33
|
...buttonProps,
|
|
42
|
-
|
|
34
|
+
ref,
|
|
35
|
+
className: `${BASE_CSS} ${getButtonCss(pattern)} ${className} ${buttonProps?.className ?? ""}`,
|
|
43
36
|
type: buttonProps?.type ?? "button",
|
|
44
|
-
onClick: handleClick,
|
|
45
37
|
children: [
|
|
46
38
|
leftIcon && /* @__PURE__ */ jsx(Icon, { ...leftIcon }),
|
|
47
39
|
typeof content === "object" ? /* @__PURE__ */ jsx(Icon, { ...content }) : content,
|
|
48
40
|
rightIcon && /* @__PURE__ */ jsx(Icon, { ...rightIcon })
|
|
49
41
|
]
|
|
50
42
|
}
|
|
51
|
-
)
|
|
52
|
-
|
|
43
|
+
)
|
|
44
|
+
), "Button");
|
|
53
45
|
|
|
54
46
|
// src/lib/constants.ts
|
|
55
47
|
var TOOLTIP_GAP = 15;
|
|
56
48
|
var TOOLTIP_ANIMATION_DURATION = 400;
|
|
49
|
+
var TOOLTIP_VIEWPORT_MARGIN = 8;
|
|
50
|
+
var TOOLTIP_ARROW_INSET = 16;
|
|
57
51
|
var TOAST_TYPES = [
|
|
58
52
|
"success",
|
|
59
53
|
"error",
|
|
@@ -67,6 +61,13 @@ var TOAST_TYPES = [
|
|
|
67
61
|
Object.fromEntries(TOAST_TYPES.map((type) => [type, type]));
|
|
68
62
|
|
|
69
63
|
// src/lib/tooltip.ts
|
|
64
|
+
var OPPOSITE = {
|
|
65
|
+
top: "bottom",
|
|
66
|
+
bottom: "top",
|
|
67
|
+
left: "right",
|
|
68
|
+
right: "left"
|
|
69
|
+
};
|
|
70
|
+
var clamp = /* @__PURE__ */ __name((value, min, max) => Math.min(Math.max(value, min), max), "clamp");
|
|
70
71
|
var getTooltipPosition = /* @__PURE__ */ __name((rect, placement) => {
|
|
71
72
|
switch (placement) {
|
|
72
73
|
case "bottom":
|
|
@@ -80,6 +81,45 @@ var getTooltipPosition = /* @__PURE__ */ __name((rect, placement) => {
|
|
|
80
81
|
return { top: rect.top, left: rect.left + rect.width / 2 };
|
|
81
82
|
}
|
|
82
83
|
}, "getTooltipPosition");
|
|
84
|
+
var fitsOn = /* @__PURE__ */ __name((rect, size, placement, viewport) => {
|
|
85
|
+
switch (placement) {
|
|
86
|
+
case "bottom":
|
|
87
|
+
return rect.bottom + TOOLTIP_GAP + size.height <= viewport.height - TOOLTIP_VIEWPORT_MARGIN;
|
|
88
|
+
case "right":
|
|
89
|
+
return rect.right + TOOLTIP_GAP + size.width <= viewport.width - TOOLTIP_VIEWPORT_MARGIN;
|
|
90
|
+
case "left":
|
|
91
|
+
return rect.left - TOOLTIP_GAP - size.width >= TOOLTIP_VIEWPORT_MARGIN;
|
|
92
|
+
case "top":
|
|
93
|
+
default:
|
|
94
|
+
return rect.top - TOOLTIP_GAP - size.height >= TOOLTIP_VIEWPORT_MARGIN;
|
|
95
|
+
}
|
|
96
|
+
}, "fitsOn");
|
|
97
|
+
var getTooltipLayout = /* @__PURE__ */ __name(({
|
|
98
|
+
rect,
|
|
99
|
+
placement,
|
|
100
|
+
size,
|
|
101
|
+
viewport
|
|
102
|
+
}) => {
|
|
103
|
+
if (!size) return { placement, position: getTooltipPosition(rect, placement), arrowShift: 0 };
|
|
104
|
+
const side = !fitsOn(rect, size, placement, viewport) && fitsOn(rect, size, OPPOSITE[placement], viewport) ? OPPOSITE[placement] : placement;
|
|
105
|
+
const position = getTooltipPosition(rect, side);
|
|
106
|
+
const isHorizontal = side === "top" || side === "bottom";
|
|
107
|
+
const length = isHorizontal ? size.width : size.height;
|
|
108
|
+
const limit = isHorizontal ? viewport.width : viewport.height;
|
|
109
|
+
const start = (isHorizontal ? position.left : position.top) - length / 2;
|
|
110
|
+
const min = TOOLTIP_VIEWPORT_MARGIN;
|
|
111
|
+
const max = limit - TOOLTIP_VIEWPORT_MARGIN - length;
|
|
112
|
+
const shift = max < min ? 0 : clamp(start, min, max) - start;
|
|
113
|
+
const reach = Math.max(0, length / 2 - TOOLTIP_ARROW_INSET);
|
|
114
|
+
const arrowShift = clamp(-shift, -reach, reach);
|
|
115
|
+
return {
|
|
116
|
+
placement: side,
|
|
117
|
+
position: isHorizontal ? { top: position.top, left: position.left + shift } : { top: position.top + shift, left: position.left },
|
|
118
|
+
arrowShift: arrowShift === 0 ? 0 : arrowShift
|
|
119
|
+
// never -0
|
|
120
|
+
};
|
|
121
|
+
}, "getTooltipLayout");
|
|
122
|
+
var isSameTooltipLayout = /* @__PURE__ */ __name((a, b) => a.placement === b.placement && a.arrowShift === b.arrowShift && a.position.top === b.position.top && a.position.left === b.position.left, "isSameTooltipLayout");
|
|
83
123
|
var getTooltipTransform = /* @__PURE__ */ __name((placement, isVisible) => {
|
|
84
124
|
const gap = String(isVisible ? TOOLTIP_GAP : 0);
|
|
85
125
|
switch (placement) {
|
|
@@ -97,90 +137,134 @@ var getTooltipTransform = /* @__PURE__ */ __name((placement, isVisible) => {
|
|
|
97
137
|
var getTooltipArrowCss = /* @__PURE__ */ __name((placement) => {
|
|
98
138
|
switch (placement) {
|
|
99
139
|
case "bottom":
|
|
100
|
-
return "after:bottom-full after:left-
|
|
140
|
+
return "after:bottom-full after:left-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:-translate-x-1/2 after:border-b-silver-jet-2";
|
|
101
141
|
case "right":
|
|
102
|
-
return "after:top-
|
|
142
|
+
return "after:top-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:right-full after:-translate-y-1/2 after:border-r-silver-jet-2";
|
|
103
143
|
case "left":
|
|
104
|
-
return "after:top-
|
|
144
|
+
return "after:top-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:left-full after:-translate-y-1/2 after:border-l-silver-jet-2";
|
|
105
145
|
case "top":
|
|
106
146
|
default:
|
|
107
|
-
return "after:top-full after:left-
|
|
147
|
+
return "after:top-full after:left-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:-translate-x-1/2 after:border-t-silver-jet-2";
|
|
108
148
|
}
|
|
109
149
|
}, "getTooltipArrowCss");
|
|
110
|
-
var
|
|
150
|
+
var Tooltip = /* @__PURE__ */ __name(({
|
|
111
151
|
title,
|
|
112
152
|
description,
|
|
113
153
|
children,
|
|
114
154
|
className = "",
|
|
115
155
|
containerClassName = "",
|
|
116
|
-
placement = "top"
|
|
156
|
+
placement = "top",
|
|
157
|
+
required = true
|
|
117
158
|
}) => {
|
|
159
|
+
const id = useId();
|
|
118
160
|
const containerRef = useRef(null);
|
|
161
|
+
const tooltipRef = useRef(null);
|
|
119
162
|
const [isOpen, setIsOpen] = useState(false);
|
|
120
163
|
const [isVisible, setIsVisible] = useState(false);
|
|
121
|
-
const [
|
|
164
|
+
const [layout, setLayout] = useState();
|
|
122
165
|
const closeTimerRef = useRef(void 0);
|
|
123
166
|
const animationFrameRef = useRef(void 0);
|
|
124
|
-
const
|
|
167
|
+
const updateLayout = useCallback(() => {
|
|
125
168
|
const container = containerRef.current;
|
|
126
169
|
if (!container) return;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
setIsVisible(true);
|
|
133
|
-
});
|
|
170
|
+
const next = getTooltipLayout({
|
|
171
|
+
rect: container.getBoundingClientRect(),
|
|
172
|
+
placement,
|
|
173
|
+
size: tooltipRef.current?.getBoundingClientRect(),
|
|
174
|
+
viewport: { width: window.innerWidth, height: window.innerHeight }
|
|
134
175
|
});
|
|
135
|
-
|
|
176
|
+
setLayout((current) => current && isSameTooltipLayout(current, next) ? current : next);
|
|
177
|
+
}, [placement]);
|
|
136
178
|
const openTooltip = /* @__PURE__ */ __name(() => {
|
|
179
|
+
if (!required) return;
|
|
137
180
|
if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
|
|
138
181
|
if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
|
|
139
|
-
|
|
182
|
+
updateLayout();
|
|
140
183
|
setIsVisible(false);
|
|
141
184
|
setIsOpen(true);
|
|
142
|
-
|
|
185
|
+
animationFrameRef.current = window.requestAnimationFrame(() => {
|
|
186
|
+
animationFrameRef.current = window.requestAnimationFrame(() => {
|
|
187
|
+
setIsVisible(true);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
143
190
|
}, "openTooltip");
|
|
144
|
-
const closeTooltip =
|
|
191
|
+
const closeTooltip = useCallback(() => {
|
|
145
192
|
if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
|
|
193
|
+
if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
|
|
146
194
|
setIsVisible(false);
|
|
147
195
|
closeTimerRef.current = window.setTimeout(() => {
|
|
148
196
|
setIsOpen(false);
|
|
149
197
|
}, TOOLTIP_ANIMATION_DURATION);
|
|
150
|
-
},
|
|
198
|
+
}, []);
|
|
151
199
|
useEffect(() => {
|
|
152
200
|
return () => {
|
|
153
201
|
if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
|
|
154
202
|
if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
|
|
155
203
|
};
|
|
156
204
|
}, []);
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!isOpen) return;
|
|
207
|
+
const closeOnEscape = /* @__PURE__ */ __name((event) => {
|
|
208
|
+
if (event.key === "Escape") closeTooltip();
|
|
209
|
+
}, "closeOnEscape");
|
|
210
|
+
document.addEventListener("keydown", closeOnEscape);
|
|
211
|
+
return () => {
|
|
212
|
+
document.removeEventListener("keydown", closeOnEscape);
|
|
213
|
+
};
|
|
214
|
+
}, [isOpen, closeTooltip]);
|
|
157
215
|
useLayoutEffect(() => {
|
|
158
216
|
if (!isOpen) return;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
217
|
+
updateLayout();
|
|
218
|
+
let frame;
|
|
219
|
+
const scheduleUpdate = /* @__PURE__ */ __name(() => {
|
|
220
|
+
if (frame) return;
|
|
221
|
+
frame = window.requestAnimationFrame(() => {
|
|
222
|
+
frame = void 0;
|
|
223
|
+
updateLayout();
|
|
224
|
+
});
|
|
225
|
+
}, "scheduleUpdate");
|
|
226
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
227
|
+
window.addEventListener("scroll", scheduleUpdate, true);
|
|
162
228
|
return () => {
|
|
163
|
-
window.removeEventListener("resize",
|
|
164
|
-
window.removeEventListener("scroll",
|
|
229
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
230
|
+
window.removeEventListener("scroll", scheduleUpdate, true);
|
|
231
|
+
if (frame) window.cancelAnimationFrame(frame);
|
|
165
232
|
};
|
|
166
|
-
}, [isOpen,
|
|
233
|
+
}, [isOpen, updateLayout]);
|
|
234
|
+
const trigger = useMemo(() => {
|
|
235
|
+
if (!required || !isOpen || !isValidElement(children)) {
|
|
236
|
+
return children;
|
|
237
|
+
}
|
|
238
|
+
const describedBy = children.props["aria-describedby"];
|
|
239
|
+
return cloneElement(children, {
|
|
240
|
+
"aria-describedby": describedBy ? `${describedBy} ${id}` : id
|
|
241
|
+
});
|
|
242
|
+
}, [children, id, isOpen, required]);
|
|
243
|
+
const isShown = required && isOpen && layout !== void 0;
|
|
167
244
|
return /* @__PURE__ */ jsxs(
|
|
168
245
|
"div",
|
|
169
246
|
{
|
|
170
247
|
ref: containerRef,
|
|
171
|
-
className: `relative inline-flex ${containerClassName}
|
|
248
|
+
className: required ? `relative inline-flex ${containerClassName}` : "contents",
|
|
172
249
|
onBlur: closeTooltip,
|
|
173
250
|
onFocus: openTooltip,
|
|
174
251
|
onMouseEnter: openTooltip,
|
|
175
252
|
onMouseLeave: closeTooltip,
|
|
176
253
|
children: [
|
|
177
|
-
|
|
178
|
-
|
|
254
|
+
trigger,
|
|
255
|
+
isShown && createPortal(
|
|
179
256
|
/* @__PURE__ */ jsxs(
|
|
180
257
|
"div",
|
|
181
258
|
{
|
|
182
|
-
|
|
183
|
-
|
|
259
|
+
ref: tooltipRef,
|
|
260
|
+
id,
|
|
261
|
+
role: "tooltip",
|
|
262
|
+
className: `border-silver-jet-2 bg-platinum-jet fixed z-9999 flex flex-col items-center justify-center rounded-lg border px-3 py-2 text-center whitespace-nowrap backdrop-blur-md transition-all duration-400 ease-in-out after:absolute after:border-8 after:border-solid after:border-transparent after:content-[''] ${isVisible ? "visible opacity-100" : "invisible opacity-0"} ${getTooltipArrowCss(layout.placement)} ${className} `,
|
|
263
|
+
style: {
|
|
264
|
+
...layout.position,
|
|
265
|
+
"--tooltip-arrow-shift": `${String(layout.arrowShift)}px`,
|
|
266
|
+
transform: getTooltipTransform(layout.placement, isVisible)
|
|
267
|
+
},
|
|
184
268
|
children: [
|
|
185
269
|
/* @__PURE__ */ jsx("div", { className: "text-primary text-xs font-medium", children: title }),
|
|
186
270
|
description && /* @__PURE__ */ jsx("div", { className: "text-tertiary text-[10px] font-light", children: description })
|
|
@@ -192,9 +276,6 @@ var OriginalTooltip = /* @__PURE__ */ __name(({
|
|
|
192
276
|
]
|
|
193
277
|
}
|
|
194
278
|
);
|
|
195
|
-
}, "OriginalTooltip");
|
|
196
|
-
var Tooltip = /* @__PURE__ */ __name(({ children, required = true, ...props }) => {
|
|
197
|
-
return required ? /* @__PURE__ */ jsx(OriginalTooltip, { ...props, children }) : children;
|
|
198
279
|
}, "Tooltip");
|
|
199
280
|
|
|
200
281
|
export { Button, Tooltip };
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/button.ts","../../src/ui/Button.tsx","../../src/lib/constants.ts","../../src/lib/tooltip.ts","../../src/ui/Tooltip.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { IButton } from '../types/component.js';\n\nexport const getButtonCss = (pattern: IButton['pattern']) => {\n switch (pattern) {\n case 'primary':\n return 'text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover';\n case 'secondary':\n return 'text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover';\n case 'tertiary':\n return 'text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover';\n case 'outline':\n return 'text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover';\n case 'transparent':\n default:\n return 'bg-transparent border border-primary/30 text-secondary';\n }\n};\n","import { Icon } from '@iconify/react';\nimport { type MouseEvent } from 'react';\n\nimport { getButtonCss } from '../lib/button.js';\nimport type { IButton } from '../types/component.js';\n\nexport const Button = ({\n pattern,\n content,\n className = '',\n leftIcon,\n rightIcon,\n buttonProps,\n}: IButton) => {\n const btnCSS = getButtonCss(pattern);\n\n const handleClick = (e: MouseEvent<HTMLButtonElement>) => {\n if (buttonProps?.disabled) return;\n buttonProps?.onClick?.(e);\n };\n\n return (\n <button\n {...buttonProps}\n className={`group flex w-full cursor-pointer items-center justify-center gap-1 rounded-lg px-4 py-3 text-sm leading-4 transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-85 lg:px-5 lg:py-4 xl:text-base ${btnCSS} ${className} ${\n buttonProps?.className ?? ''\n }`}\n type={buttonProps?.type ?? 'button'}\n onClick={handleClick}\n >\n {leftIcon && <Icon {...leftIcon} />}\n {typeof content === 'object' ? <Icon {...content} /> : content}\n {rightIcon && <Icon {...rightIcon} />}\n </button>\n );\n};\n","export const TOOLTIP_GAP = 15 as const;\nexport const TOOLTIP_ANIMATION_DURATION = 400 as const;\n\nexport const TOAST_TYPES = [\n 'success',\n 'error',\n 'warning',\n 'progress',\n 'uploads',\n 'loading',\n 'default',\n 'custom',\n] as const;\n\nexport const TOAST_TYPE = Object.fromEntries(TOAST_TYPES.map((type) => [type, type])) as {\n [K in (typeof TOAST_TYPES)[number]]: K;\n};\n","import type { CSSProperties } from 'react';\n\nimport type { ITooltip } from '../types/component.js';\nimport { TOOLTIP_GAP } from './constants.js';\n\nexport const getTooltipPosition = (\n rect: DOMRect,\n placement: NonNullable<ITooltip['placement']>,\n): CSSProperties => {\n switch (placement) {\n case 'bottom':\n return { top: rect.bottom, left: rect.left + rect.width / 2 };\n case 'right':\n return { top: rect.top + rect.height / 2, left: rect.right };\n case 'left':\n return { top: rect.top + rect.height / 2, left: rect.left };\n case 'top':\n default:\n return { top: rect.top, left: rect.left + rect.width / 2 };\n }\n};\n\nexport const getTooltipTransform = (\n placement: NonNullable<ITooltip['placement']>,\n isVisible: boolean,\n) => {\n const gap = String(isVisible ? TOOLTIP_GAP : 0);\n\n switch (placement) {\n case 'bottom':\n return `translate(-50%, ${gap}px)`;\n case 'right':\n return `translate(${gap}px, -50%)`;\n case 'left':\n return `translate(calc(-100% - ${gap}px), -50%)`;\n case 'top':\n default:\n return `translate(-50%, calc(-100% - ${gap}px))`;\n }\n};\n\nexport const getTooltipArrowCss = (placement: NonNullable<ITooltip['placement']>) => {\n switch (placement) {\n case 'bottom':\n return 'after:bottom-full after:left-1/2 after:-translate-x-1/2 after:border-b-silver-jet-2';\n case 'right':\n return 'after:top-1/2 after:right-full after:-translate-y-1/2 after:border-r-silver-jet-2';\n case 'left':\n return 'after:top-1/2 after:left-full after:-translate-y-1/2 after:border-l-silver-jet-2';\n case 'top':\n default:\n return 'after:top-full after:left-1/2 after:-translate-x-1/2 after:border-t-silver-jet-2';\n }\n};\n","import {\n type CSSProperties,\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { TOOLTIP_ANIMATION_DURATION } from '../lib/constants.js';\nimport { getTooltipArrowCss, getTooltipPosition, getTooltipTransform } from '../lib/tooltip.js';\nimport type { ITooltip } from '../types/component.js';\n\nconst OriginalTooltip = ({\n title,\n description,\n children,\n className = '',\n containerClassName = '',\n placement = 'top',\n}: Omit<ITooltip, 'required'>) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [tooltipStyle, setTooltipStyle] = useState<CSSProperties>();\n const closeTimerRef = useRef<number | undefined>(undefined);\n const animationFrameRef = useRef<number | undefined>(undefined);\n\n const updateTooltipPosition = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n setTooltipStyle(getTooltipPosition(container.getBoundingClientRect(), placement));\n }, [placement]);\n\n const showTooltip = () => {\n animationFrameRef.current = window.requestAnimationFrame(() => {\n animationFrameRef.current = window.requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n };\n\n const openTooltip = () => {\n if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n\n updateTooltipPosition();\n setIsVisible(false);\n setIsOpen(true);\n showTooltip();\n };\n\n const closeTooltip = () => {\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n\n setIsVisible(false);\n closeTimerRef.current = window.setTimeout(() => {\n setIsOpen(false);\n }, TOOLTIP_ANIMATION_DURATION);\n };\n\n useEffect(() => {\n return () => {\n if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n };\n }, []);\n\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n updateTooltipPosition();\n window.addEventListener('resize', updateTooltipPosition);\n window.addEventListener('scroll', updateTooltipPosition, true);\n\n return () => {\n window.removeEventListener('resize', updateTooltipPosition);\n window.removeEventListener('scroll', updateTooltipPosition, true);\n };\n }, [isOpen, updateTooltipPosition]);\n\n return (\n <div\n ref={containerRef}\n className={`relative inline-flex ${containerClassName}`}\n onBlur={closeTooltip}\n onFocus={openTooltip}\n onMouseEnter={openTooltip}\n onMouseLeave={closeTooltip}\n >\n {/* Main Content */}\n {children}\n {/* Tooltip */}\n {isOpen &&\n tooltipStyle &&\n createPortal(\n <div\n className={`border-silver-jet-2 bg-platinum-jet fixed z-9999 flex flex-col items-center justify-center rounded-lg border px-3 py-2 text-center whitespace-nowrap backdrop-blur-md transition-all duration-400 ease-in-out after:absolute after:border-8 after:border-solid after:border-transparent after:content-[''] ${isVisible ? 'visible opacity-100' : 'invisible opacity-0'} ${getTooltipArrowCss(placement)} ${className} `}\n style={{ ...tooltipStyle, transform: getTooltipTransform(placement, isVisible) }}\n >\n <div className=\"text-primary text-xs font-medium\">{title}</div>\n {description && (\n <div className=\"text-tertiary text-[10px] font-light\">{description}</div>\n )}\n </div>,\n document.body,\n )}\n </div>\n );\n};\n\nexport const Tooltip = ({ children, required = true, ...props }: ITooltip) => {\n return required ? <OriginalTooltip {...props}>{children}</OriginalTooltip> : children;\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/button.ts","../../src/ui/Button.tsx","../../src/lib/constants.ts","../../src/lib/tooltip.ts","../../src/ui/Tooltip.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { IButton } from '../types/component.js';\n\ntype TPattern = IButton['pattern'];\n\n// A `Record`: adding a pattern to the type is a type error here until it has a look.\nconst PATTERN_CSS: Record<TPattern, string> = {\n primary: 'text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover',\n secondary:\n 'text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover',\n tertiary: 'text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover',\n outline: 'text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover',\n transparent: 'bg-transparent border border-primary/30 text-secondary',\n};\n\n/** The look of a pattern; a pattern nobody knows (from code that is not type checked) is transparent. */\nexport const getButtonCss = (pattern: TPattern) =>\n Object.hasOwn(PATTERN_CSS, pattern) ? PATTERN_CSS[pattern] : PATTERN_CSS.transparent;\n","import { Icon } from '@iconify/react';\n\nimport { getButtonCss } from '../lib/button.js';\nimport type { IButton } from '../types/component.js';\n\nconst BASE_CSS =\n 'group flex w-full cursor-pointer items-center justify-center gap-1 rounded-lg px-4 py-3 text-sm leading-4 transition-all duration-300 disabled:cursor-not-allowed disabled:opacity-85 lg:px-5 lg:py-4 xl:text-base';\n\nexport const Button = ({\n pattern,\n content,\n className = '',\n leftIcon,\n rightIcon,\n buttonProps,\n ref,\n}: IButton) => (\n // The classes that are given come last but do not remove the button's own: to win over one of\n // them give it with `!` (`rounded-md!`), the way the toast's button does.\n <button\n {...buttonProps}\n ref={ref}\n className={`${BASE_CSS} ${getButtonCss(pattern)} ${className} ${buttonProps?.className ?? ''}`}\n type={buttonProps?.type ?? 'button'}\n >\n {leftIcon && <Icon {...leftIcon} />}\n {typeof content === 'object' ? <Icon {...content} /> : content}\n {rightIcon && <Icon {...rightIcon} />}\n </button>\n);\n","export const TOOLTIP_GAP = 15 as const;\nexport const TOOLTIP_ANIMATION_DURATION = 400 as const;\n/** The least space a tooltip keeps between itself and the edge of the window. */\nexport const TOOLTIP_VIEWPORT_MARGIN = 8 as const;\n/** The arrow stays at least this far from the ends of the tooltip (its own size and the rounded corner). */\nexport const TOOLTIP_ARROW_INSET = 16 as const;\n\nexport const TOAST_TYPES = [\n 'success',\n 'error',\n 'warning',\n 'progress',\n 'uploads',\n 'loading',\n 'default',\n 'custom',\n] as const;\n\nexport const TOAST_TYPE = Object.fromEntries(TOAST_TYPES.map((type) => [type, type])) as {\n [K in (typeof TOAST_TYPES)[number]]: K;\n};\n","import type { ITooltip } from '../types/component.js';\nimport { TOOLTIP_ARROW_INSET, TOOLTIP_GAP, TOOLTIP_VIEWPORT_MARGIN } from './constants.js';\n\ntype TPlacement = NonNullable<ITooltip['placement']>;\n\nexport interface ISize {\n width: number;\n height: number;\n}\n\nexport interface ITooltipLayout {\n /** The side the tooltip really opens on: the opposite one when there is no room on the asked one. */\n placement: TPlacement;\n /** Where the tooltip points to, in the window (it is moved away from it by `getTooltipTransform`). */\n position: { top: number; left: number };\n /** How far the arrow moves along the tooltip to keep pointing at the content when the tooltip is pushed in. */\n arrowShift: number;\n}\n\nconst OPPOSITE: Record<TPlacement, TPlacement> = {\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n};\n\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\nexport const getTooltipPosition = (\n rect: DOMRect,\n placement: TPlacement,\n): { top: number; left: number } => {\n switch (placement) {\n case 'bottom':\n return { top: rect.bottom, left: rect.left + rect.width / 2 };\n case 'right':\n return { top: rect.top + rect.height / 2, left: rect.right };\n case 'left':\n return { top: rect.top + rect.height / 2, left: rect.left };\n case 'top':\n default:\n return { top: rect.top, left: rect.left + rect.width / 2 };\n }\n};\n\n/** Whether a tooltip of this size, on this side of the content, stays inside the window. */\nconst fitsOn = (rect: DOMRect, size: ISize, placement: TPlacement, viewport: ISize) => {\n switch (placement) {\n case 'bottom':\n return rect.bottom + TOOLTIP_GAP + size.height <= viewport.height - TOOLTIP_VIEWPORT_MARGIN;\n case 'right':\n return rect.right + TOOLTIP_GAP + size.width <= viewport.width - TOOLTIP_VIEWPORT_MARGIN;\n case 'left':\n return rect.left - TOOLTIP_GAP - size.width >= TOOLTIP_VIEWPORT_MARGIN;\n case 'top':\n default:\n return rect.top - TOOLTIP_GAP - size.height >= TOOLTIP_VIEWPORT_MARGIN;\n }\n};\n\n/**\n * Where the tooltip goes. Without its `size` (it is not on the page yet) it goes where it was asked\n * to. With it, it opens on the opposite side when the asked one has no room and the other has, and\n * it is pushed along that side to stay inside the window; the arrow keeps pointing at the content.\n */\nexport const getTooltipLayout = ({\n rect,\n placement,\n size,\n viewport,\n}: {\n rect: DOMRect;\n placement: TPlacement;\n size?: ISize;\n viewport: ISize;\n}): ITooltipLayout => {\n if (!size) return { placement, position: getTooltipPosition(rect, placement), arrowShift: 0 };\n\n const side =\n !fitsOn(rect, size, placement, viewport) && fitsOn(rect, size, OPPOSITE[placement], viewport)\n ? OPPOSITE[placement]\n : placement;\n const position = getTooltipPosition(rect, side);\n\n const isHorizontal = side === 'top' || side === 'bottom';\n const length = isHorizontal ? size.width : size.height;\n const limit = isHorizontal ? viewport.width : viewport.height;\n const start = (isHorizontal ? position.left : position.top) - length / 2;\n const min = TOOLTIP_VIEWPORT_MARGIN;\n const max = limit - TOOLTIP_VIEWPORT_MARGIN - length;\n // a tooltip bigger than the window cannot be pushed in, it is left where it is\n const shift = max < min ? 0 : clamp(start, min, max) - start;\n const reach = Math.max(0, length / 2 - TOOLTIP_ARROW_INSET);\n const arrowShift = clamp(-shift, -reach, reach);\n\n return {\n placement: side,\n position: isHorizontal\n ? { top: position.top, left: position.left + shift }\n : { top: position.top + shift, left: position.left },\n arrowShift: arrowShift === 0 ? 0 : arrowShift, // never -0\n };\n};\n\n/** Whether two layouts put the tooltip in the same place (so there is nothing to change). */\nexport const isSameTooltipLayout = (a: ITooltipLayout, b: ITooltipLayout) =>\n a.placement === b.placement &&\n a.arrowShift === b.arrowShift &&\n a.position.top === b.position.top &&\n a.position.left === b.position.left;\n\nexport const getTooltipTransform = (placement: TPlacement, isVisible: boolean) => {\n const gap = String(isVisible ? TOOLTIP_GAP : 0);\n\n switch (placement) {\n case 'bottom':\n return `translate(-50%, ${gap}px)`;\n case 'right':\n return `translate(${gap}px, -50%)`;\n case 'left':\n return `translate(calc(-100% - ${gap}px), -50%)`;\n case 'top':\n default:\n return `translate(-50%, calc(-100% - ${gap}px))`;\n }\n};\n\n// The arrow sits in the middle of its side plus `--tooltip-arrow-shift` (set by the tooltip).\nexport const getTooltipArrowCss = (placement: TPlacement) => {\n switch (placement) {\n case 'bottom':\n return 'after:bottom-full after:left-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:-translate-x-1/2 after:border-b-silver-jet-2';\n case 'right':\n return 'after:top-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:right-full after:-translate-y-1/2 after:border-r-silver-jet-2';\n case 'left':\n return 'after:top-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:left-full after:-translate-y-1/2 after:border-l-silver-jet-2';\n case 'top':\n default:\n return 'after:top-full after:left-[calc(50%_+_var(--tooltip-arrow-shift,0px))] after:-translate-x-1/2 after:border-t-silver-jet-2';\n }\n};\n","import {\n cloneElement,\n type CSSProperties,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { TOOLTIP_ANIMATION_DURATION } from '../lib/constants.js';\nimport {\n getTooltipArrowCss,\n getTooltipLayout,\n getTooltipTransform,\n isSameTooltipLayout,\n type ITooltipLayout,\n} from '../lib/tooltip.js';\nimport type { ITooltip } from '../types/component.js';\n\n/*\n * `required={false}` keeps the wrapper (as `display: contents`, so it takes no room) instead of\n * returning the content alone: the content then stays in the same place of the tree when `required`\n * changes, and keeps its state (an open select, a focused input) instead of being created again.\n */\nexport const Tooltip = ({\n title,\n description,\n children,\n className = '',\n containerClassName = '',\n placement = 'top',\n required = true,\n}: ITooltip) => {\n const id = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [layout, setLayout] = useState<ITooltipLayout>();\n const closeTimerRef = useRef<number | undefined>(undefined);\n const animationFrameRef = useRef<number | undefined>(undefined);\n\n // Places the tooltip from where the content is now and, once the tooltip is on the page, from its\n // size too, so it can open on the other side or be pushed in when it would leave the window.\n const updateLayout = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const next = getTooltipLayout({\n rect: container.getBoundingClientRect(),\n placement,\n size: tooltipRef.current?.getBoundingClientRect(),\n viewport: { width: window.innerWidth, height: window.innerHeight },\n });\n\n // the same place again: no new state, so no render\n setLayout((current) => (current && isSameTooltipLayout(current, next) ? current : next));\n }, [placement]);\n\n const openTooltip = () => {\n if (!required) return;\n\n if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n\n updateLayout();\n setIsVisible(false);\n setIsOpen(true);\n // two frames, so the browser has drawn it hidden first and the fade has something to start from\n animationFrameRef.current = window.requestAnimationFrame(() => {\n animationFrameRef.current = window.requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n };\n\n const closeTooltip = useCallback(() => {\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n // Without this, a close that was asked twice (the mouse leaves, then the focus does) leaves the\n // first timer running with nobody holding it: it closes a tooltip the mouse has come back to.\n if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);\n\n setIsVisible(false);\n closeTimerRef.current = window.setTimeout(() => {\n setIsOpen(false);\n }, TOOLTIP_ANIMATION_DURATION);\n }, []);\n\n useEffect(() => {\n return () => {\n if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);\n if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);\n };\n }, []);\n\n // Escape closes it, as a person who cannot move the mouse away has no other way to (WCAG 1.4.13).\n useEffect(() => {\n if (!isOpen) return;\n\n const closeOnEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') closeTooltip();\n };\n\n document.addEventListener('keydown', closeOnEscape);\n\n return () => {\n document.removeEventListener('keydown', closeOnEscape);\n };\n }, [isOpen, closeTooltip]);\n\n // Follows the content while the window is resized or scrolled: at most once a frame, however\n // many scroll events there are.\n useLayoutEffect(() => {\n if (!isOpen) return;\n\n updateLayout();\n\n let frame: number | undefined;\n const scheduleUpdate = () => {\n if (frame) return;\n\n frame = window.requestAnimationFrame(() => {\n frame = undefined;\n updateLayout();\n });\n };\n\n window.addEventListener('resize', scheduleUpdate);\n window.addEventListener('scroll', scheduleUpdate, true);\n\n return () => {\n window.removeEventListener('resize', scheduleUpdate);\n window.removeEventListener('scroll', scheduleUpdate, true);\n if (frame) window.cancelAnimationFrame(frame);\n };\n }, [isOpen, updateLayout]);\n\n // While the tooltip is there, the content is described by it (read by a screen reader when the\n // content has the focus). `useMemo` keeps the element the same between the renders the tooltip\n // makes by itself, so the content is not rendered again for each of them.\n const trigger = useMemo(() => {\n if (!required || !isOpen || !isValidElement<{ 'aria-describedby'?: string }>(children)) {\n return children;\n }\n\n const describedBy = children.props['aria-describedby'];\n\n return cloneElement(children, {\n 'aria-describedby': describedBy ? `${describedBy} ${id}` : id,\n });\n }, [children, id, isOpen, required]);\n\n const isShown = required && isOpen && layout !== undefined;\n\n return (\n <div\n ref={containerRef}\n className={required ? `relative inline-flex ${containerClassName}` : 'contents'}\n onBlur={closeTooltip}\n onFocus={openTooltip}\n onMouseEnter={openTooltip}\n onMouseLeave={closeTooltip}\n >\n {/* Main Content */}\n {trigger}\n {/* Tooltip */}\n {isShown &&\n createPortal(\n <div\n ref={tooltipRef}\n id={id}\n role=\"tooltip\"\n className={`border-silver-jet-2 bg-platinum-jet fixed z-9999 flex flex-col items-center justify-center rounded-lg border px-3 py-2 text-center whitespace-nowrap backdrop-blur-md transition-all duration-400 ease-in-out after:absolute after:border-8 after:border-solid after:border-transparent after:content-[''] ${isVisible ? 'visible opacity-100' : 'invisible opacity-0'} ${getTooltipArrowCss(layout.placement)} ${className} `}\n style={\n {\n ...layout.position,\n '--tooltip-arrow-shift': `${String(layout.arrowShift)}px`,\n transform: getTooltipTransform(layout.placement, isVisible),\n } as CSSProperties\n }\n >\n <div className=\"text-primary text-xs font-medium\">{title}</div>\n {description && (\n <div className=\"text-tertiary text-[10px] font-light\">{description}</div>\n )}\n </div>,\n document.body,\n )}\n </div>\n );\n};\n"]}
|