@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.
Files changed (42) hide show
  1. package/README.md +48 -1
  2. package/dist/lib/button.d.cts +4 -1
  3. package/dist/lib/button.d.cts.map +1 -1
  4. package/dist/lib/button.d.ts +4 -1
  5. package/dist/lib/button.d.ts.map +1 -1
  6. package/dist/lib/constants.d.cts +4 -0
  7. package/dist/lib/constants.d.cts.map +1 -1
  8. package/dist/lib/constants.d.ts +4 -0
  9. package/dist/lib/constants.d.ts.map +1 -1
  10. package/dist/lib/tooltip.d.cts +36 -4
  11. package/dist/lib/tooltip.d.cts.map +1 -1
  12. package/dist/lib/tooltip.d.ts +36 -4
  13. package/dist/lib/tooltip.d.ts.map +1 -1
  14. package/dist/toast/ToastContainer.d.cts +1 -1
  15. package/dist/toast/ToastContainer.d.cts.map +1 -1
  16. package/dist/toast/ToastContainer.d.ts +1 -1
  17. package/dist/toast/ToastContainer.d.ts.map +1 -1
  18. package/dist/toast/index.cjs +36 -43
  19. package/dist/toast/index.cjs.map +1 -1
  20. package/dist/toast/index.js +23 -30
  21. package/dist/toast/index.js.map +1 -1
  22. package/dist/toast/types.d.cts +2 -2
  23. package/dist/toast/types.d.cts.map +1 -1
  24. package/dist/toast/types.d.ts +2 -2
  25. package/dist/toast/types.d.ts.map +1 -1
  26. package/dist/types/component.d.cts +22 -4
  27. package/dist/types/component.d.cts.map +1 -1
  28. package/dist/types/component.d.ts +22 -4
  29. package/dist/types/component.d.ts.map +1 -1
  30. package/dist/ui/Button.d.cts +1 -1
  31. package/dist/ui/Button.d.cts.map +1 -1
  32. package/dist/ui/Button.d.ts +1 -1
  33. package/dist/ui/Button.d.ts.map +1 -1
  34. package/dist/ui/Tooltip.d.cts +1 -1
  35. package/dist/ui/Tooltip.d.cts.map +1 -1
  36. package/dist/ui/Tooltip.d.ts +1 -1
  37. package/dist/ui/Tooltip.d.ts.map +1 -1
  38. package/dist/ui/index.cjs +143 -62
  39. package/dist/ui/index.cjs.map +1 -1
  40. package/dist/ui/index.js +143 -62
  41. package/dist/ui/index.js.map +1 -1
  42. 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 getButtonCss = /* @__PURE__ */ __name((pattern) => {
11
- switch (pattern) {
12
- case "primary":
13
- return "text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover";
14
- case "secondary":
15
- return "text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover";
16
- case "tertiary":
17
- return "text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover";
18
- case "outline":
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
- const btnCSS = getButtonCss(pattern);
34
- const handleClick = /* @__PURE__ */ __name((e) => {
35
- if (buttonProps?.disabled) return;
36
- buttonProps?.onClick?.(e);
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
- 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} ${buttonProps?.className ?? ""}`,
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
- }, "Button");
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-1/2 after:-translate-x-1/2 after:border-b-silver-jet-2";
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-1/2 after:right-full after:-translate-y-1/2 after:border-r-silver-jet-2";
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-1/2 after:left-full after:-translate-y-1/2 after:border-l-silver-jet-2";
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-1/2 after:-translate-x-1/2 after:border-t-silver-jet-2";
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 OriginalTooltip = /* @__PURE__ */ __name(({
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 [tooltipStyle, setTooltipStyle] = useState();
164
+ const [layout, setLayout] = useState();
122
165
  const closeTimerRef = useRef(void 0);
123
166
  const animationFrameRef = useRef(void 0);
124
- const updateTooltipPosition = useCallback(() => {
167
+ const updateLayout = useCallback(() => {
125
168
  const container = containerRef.current;
126
169
  if (!container) return;
127
- setTooltipStyle(getTooltipPosition(container.getBoundingClientRect(), placement));
128
- }, [placement]);
129
- const showTooltip = /* @__PURE__ */ __name(() => {
130
- animationFrameRef.current = window.requestAnimationFrame(() => {
131
- animationFrameRef.current = window.requestAnimationFrame(() => {
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
- }, "showTooltip");
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
- updateTooltipPosition();
182
+ updateLayout();
140
183
  setIsVisible(false);
141
184
  setIsOpen(true);
142
- showTooltip();
185
+ animationFrameRef.current = window.requestAnimationFrame(() => {
186
+ animationFrameRef.current = window.requestAnimationFrame(() => {
187
+ setIsVisible(true);
188
+ });
189
+ });
143
190
  }, "openTooltip");
144
- const closeTooltip = /* @__PURE__ */ __name(() => {
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
- }, "closeTooltip");
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
- updateTooltipPosition();
160
- window.addEventListener("resize", updateTooltipPosition);
161
- window.addEventListener("scroll", updateTooltipPosition, true);
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", updateTooltipPosition);
164
- window.removeEventListener("scroll", updateTooltipPosition, true);
229
+ window.removeEventListener("resize", scheduleUpdate);
230
+ window.removeEventListener("scroll", scheduleUpdate, true);
231
+ if (frame) window.cancelAnimationFrame(frame);
165
232
  };
166
- }, [isOpen, updateTooltipPosition]);
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
- children,
178
- isOpen && tooltipStyle && createPortal(
254
+ trigger,
255
+ isShown && createPortal(
179
256
  /* @__PURE__ */ jsxs(
180
257
  "div",
181
258
  {
182
- 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} `,
183
- style: { ...tooltipStyle, transform: getTooltipTransform(placement, isVisible) },
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 };
@@ -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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beautinique/frontend-components",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Shared React UI components for the Beautinique frontends.",
5
5
  "keywords": [
6
6
  "frontend",