@beautinique/frontend-components 1.0.1 → 1.0.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.
Files changed (42) hide show
  1. package/README.md +49 -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 +37 -43
  19. package/dist/toast/index.cjs.map +1 -1
  20. package/dist/toast/index.js +24 -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 +144 -62
  39. package/dist/ui/index.cjs.map +1 -1
  40. package/dist/ui/index.js +144 -62
  41. package/dist/ui/index.js.map +1 -1
  42. package/package.json +2 -1
package/dist/ui/index.js CHANGED
@@ -1,59 +1,54 @@
1
1
  import { Icon } from '@iconify/react';
2
- import { useRef, useState, useCallback, useEffect, useLayoutEffect } from 'react';
2
+ import { twMerge } from 'tailwind-merge';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { useId, useRef, useState, useCallback, useEffect, useLayoutEffect, useMemo, isValidElement, cloneElement } from 'react';
4
5
  import { createPortal } from 'react-dom';
5
6
 
6
7
  var __defProp = Object.defineProperty;
7
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
9
 
9
10
  // 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");
11
+ var PATTERN_CSS = {
12
+ primary: "text-white bg-sky-blue-burst shadow-primary-btn hover:shadow-primary-btn-hover",
13
+ secondary: "text-secondary-invert bg-secondary shadow-secondary-btn hover:shadow-secondary-btn-hover",
14
+ tertiary: "text-tertiary-invert bg-tertiary shadow-tertiary-btn hover:shadow-tertiary-btn-hover",
15
+ outline: "text-primary border border-primary shadow-outline-btn hover:shadow-outline-btn-hover",
16
+ transparent: "bg-transparent border border-primary/30 text-secondary"
17
+ };
18
+ var getButtonCss = /* @__PURE__ */ __name((pattern) => Object.hasOwn(PATTERN_CSS, pattern) ? PATTERN_CSS[pattern] : PATTERN_CSS.transparent, "getButtonCss");
19
+ 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
20
  var Button = /* @__PURE__ */ __name(({
26
21
  pattern,
27
22
  content,
28
23
  className = "",
29
24
  leftIcon,
30
25
  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(
26
+ buttonProps,
27
+ ref
28
+ }) => (
29
+ // A class that is given wins over the one it clashes with (`rounded-md` over `rounded-lg`),
30
+ // with no `!`; one for another screen size (`lg:px-5`) is a different class and stays.
31
+ /* @__PURE__ */ jsxs(
39
32
  "button",
40
33
  {
41
34
  ...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 ?? ""}`,
35
+ ref,
36
+ className: twMerge(BASE_CSS, getButtonCss(pattern), className, buttonProps?.className),
43
37
  type: buttonProps?.type ?? "button",
44
- onClick: handleClick,
45
38
  children: [
46
39
  leftIcon && /* @__PURE__ */ jsx(Icon, { ...leftIcon }),
47
40
  typeof content === "object" ? /* @__PURE__ */ jsx(Icon, { ...content }) : content,
48
41
  rightIcon && /* @__PURE__ */ jsx(Icon, { ...rightIcon })
49
42
  ]
50
43
  }
51
- );
52
- }, "Button");
44
+ )
45
+ ), "Button");
53
46
 
54
47
  // src/lib/constants.ts
55
48
  var TOOLTIP_GAP = 15;
56
49
  var TOOLTIP_ANIMATION_DURATION = 400;
50
+ var TOOLTIP_VIEWPORT_MARGIN = 8;
51
+ var TOOLTIP_ARROW_INSET = 16;
57
52
  var TOAST_TYPES = [
58
53
  "success",
59
54
  "error",
@@ -67,6 +62,13 @@ var TOAST_TYPES = [
67
62
  Object.fromEntries(TOAST_TYPES.map((type) => [type, type]));
68
63
 
69
64
  // src/lib/tooltip.ts
65
+ var OPPOSITE = {
66
+ top: "bottom",
67
+ bottom: "top",
68
+ left: "right",
69
+ right: "left"
70
+ };
71
+ var clamp = /* @__PURE__ */ __name((value, min, max) => Math.min(Math.max(value, min), max), "clamp");
70
72
  var getTooltipPosition = /* @__PURE__ */ __name((rect, placement) => {
71
73
  switch (placement) {
72
74
  case "bottom":
@@ -80,6 +82,45 @@ var getTooltipPosition = /* @__PURE__ */ __name((rect, placement) => {
80
82
  return { top: rect.top, left: rect.left + rect.width / 2 };
81
83
  }
82
84
  }, "getTooltipPosition");
85
+ var fitsOn = /* @__PURE__ */ __name((rect, size, placement, viewport) => {
86
+ switch (placement) {
87
+ case "bottom":
88
+ return rect.bottom + TOOLTIP_GAP + size.height <= viewport.height - TOOLTIP_VIEWPORT_MARGIN;
89
+ case "right":
90
+ return rect.right + TOOLTIP_GAP + size.width <= viewport.width - TOOLTIP_VIEWPORT_MARGIN;
91
+ case "left":
92
+ return rect.left - TOOLTIP_GAP - size.width >= TOOLTIP_VIEWPORT_MARGIN;
93
+ case "top":
94
+ default:
95
+ return rect.top - TOOLTIP_GAP - size.height >= TOOLTIP_VIEWPORT_MARGIN;
96
+ }
97
+ }, "fitsOn");
98
+ var getTooltipLayout = /* @__PURE__ */ __name(({
99
+ rect,
100
+ placement,
101
+ size,
102
+ viewport
103
+ }) => {
104
+ if (!size) return { placement, position: getTooltipPosition(rect, placement), arrowShift: 0 };
105
+ const side = !fitsOn(rect, size, placement, viewport) && fitsOn(rect, size, OPPOSITE[placement], viewport) ? OPPOSITE[placement] : placement;
106
+ const position = getTooltipPosition(rect, side);
107
+ const isHorizontal = side === "top" || side === "bottom";
108
+ const length = isHorizontal ? size.width : size.height;
109
+ const limit = isHorizontal ? viewport.width : viewport.height;
110
+ const start = (isHorizontal ? position.left : position.top) - length / 2;
111
+ const min = TOOLTIP_VIEWPORT_MARGIN;
112
+ const max = limit - TOOLTIP_VIEWPORT_MARGIN - length;
113
+ const shift = max < min ? 0 : clamp(start, min, max) - start;
114
+ const reach = Math.max(0, length / 2 - TOOLTIP_ARROW_INSET);
115
+ const arrowShift = clamp(-shift, -reach, reach);
116
+ return {
117
+ placement: side,
118
+ position: isHorizontal ? { top: position.top, left: position.left + shift } : { top: position.top + shift, left: position.left },
119
+ arrowShift: arrowShift === 0 ? 0 : arrowShift
120
+ // never -0
121
+ };
122
+ }, "getTooltipLayout");
123
+ 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
124
  var getTooltipTransform = /* @__PURE__ */ __name((placement, isVisible) => {
84
125
  const gap = String(isVisible ? TOOLTIP_GAP : 0);
85
126
  switch (placement) {
@@ -97,90 +138,134 @@ var getTooltipTransform = /* @__PURE__ */ __name((placement, isVisible) => {
97
138
  var getTooltipArrowCss = /* @__PURE__ */ __name((placement) => {
98
139
  switch (placement) {
99
140
  case "bottom":
100
- return "after:bottom-full after:left-1/2 after:-translate-x-1/2 after:border-b-silver-jet-2";
141
+ 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
142
  case "right":
102
- return "after:top-1/2 after:right-full after:-translate-y-1/2 after:border-r-silver-jet-2";
143
+ 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
144
  case "left":
104
- return "after:top-1/2 after:left-full after:-translate-y-1/2 after:border-l-silver-jet-2";
145
+ 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
146
  case "top":
106
147
  default:
107
- return "after:top-full after:left-1/2 after:-translate-x-1/2 after:border-t-silver-jet-2";
148
+ 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
149
  }
109
150
  }, "getTooltipArrowCss");
110
- var OriginalTooltip = /* @__PURE__ */ __name(({
151
+ var Tooltip = /* @__PURE__ */ __name(({
111
152
  title,
112
153
  description,
113
154
  children,
114
155
  className = "",
115
156
  containerClassName = "",
116
- placement = "top"
157
+ placement = "top",
158
+ required = true
117
159
  }) => {
160
+ const id = useId();
118
161
  const containerRef = useRef(null);
162
+ const tooltipRef = useRef(null);
119
163
  const [isOpen, setIsOpen] = useState(false);
120
164
  const [isVisible, setIsVisible] = useState(false);
121
- const [tooltipStyle, setTooltipStyle] = useState();
165
+ const [layout, setLayout] = useState();
122
166
  const closeTimerRef = useRef(void 0);
123
167
  const animationFrameRef = useRef(void 0);
124
- const updateTooltipPosition = useCallback(() => {
168
+ const updateLayout = useCallback(() => {
125
169
  const container = containerRef.current;
126
170
  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
- });
171
+ const next = getTooltipLayout({
172
+ rect: container.getBoundingClientRect(),
173
+ placement,
174
+ size: tooltipRef.current?.getBoundingClientRect(),
175
+ viewport: { width: window.innerWidth, height: window.innerHeight }
134
176
  });
135
- }, "showTooltip");
177
+ setLayout((current) => current && isSameTooltipLayout(current, next) ? current : next);
178
+ }, [placement]);
136
179
  const openTooltip = /* @__PURE__ */ __name(() => {
180
+ if (!required) return;
137
181
  if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
138
182
  if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
139
- updateTooltipPosition();
183
+ updateLayout();
140
184
  setIsVisible(false);
141
185
  setIsOpen(true);
142
- showTooltip();
186
+ animationFrameRef.current = window.requestAnimationFrame(() => {
187
+ animationFrameRef.current = window.requestAnimationFrame(() => {
188
+ setIsVisible(true);
189
+ });
190
+ });
143
191
  }, "openTooltip");
144
- const closeTooltip = /* @__PURE__ */ __name(() => {
192
+ const closeTooltip = useCallback(() => {
145
193
  if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
194
+ if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
146
195
  setIsVisible(false);
147
196
  closeTimerRef.current = window.setTimeout(() => {
148
197
  setIsOpen(false);
149
198
  }, TOOLTIP_ANIMATION_DURATION);
150
- }, "closeTooltip");
199
+ }, []);
151
200
  useEffect(() => {
152
201
  return () => {
153
202
  if (closeTimerRef.current) window.clearTimeout(closeTimerRef.current);
154
203
  if (animationFrameRef.current) window.cancelAnimationFrame(animationFrameRef.current);
155
204
  };
156
205
  }, []);
206
+ useEffect(() => {
207
+ if (!isOpen) return;
208
+ const closeOnEscape = /* @__PURE__ */ __name((event) => {
209
+ if (event.key === "Escape") closeTooltip();
210
+ }, "closeOnEscape");
211
+ document.addEventListener("keydown", closeOnEscape);
212
+ return () => {
213
+ document.removeEventListener("keydown", closeOnEscape);
214
+ };
215
+ }, [isOpen, closeTooltip]);
157
216
  useLayoutEffect(() => {
158
217
  if (!isOpen) return;
159
- updateTooltipPosition();
160
- window.addEventListener("resize", updateTooltipPosition);
161
- window.addEventListener("scroll", updateTooltipPosition, true);
218
+ updateLayout();
219
+ let frame;
220
+ const scheduleUpdate = /* @__PURE__ */ __name(() => {
221
+ if (frame) return;
222
+ frame = window.requestAnimationFrame(() => {
223
+ frame = void 0;
224
+ updateLayout();
225
+ });
226
+ }, "scheduleUpdate");
227
+ window.addEventListener("resize", scheduleUpdate);
228
+ window.addEventListener("scroll", scheduleUpdate, true);
162
229
  return () => {
163
- window.removeEventListener("resize", updateTooltipPosition);
164
- window.removeEventListener("scroll", updateTooltipPosition, true);
230
+ window.removeEventListener("resize", scheduleUpdate);
231
+ window.removeEventListener("scroll", scheduleUpdate, true);
232
+ if (frame) window.cancelAnimationFrame(frame);
165
233
  };
166
- }, [isOpen, updateTooltipPosition]);
234
+ }, [isOpen, updateLayout]);
235
+ const trigger = useMemo(() => {
236
+ if (!required || !isOpen || !isValidElement(children)) {
237
+ return children;
238
+ }
239
+ const describedBy = children.props["aria-describedby"];
240
+ return cloneElement(children, {
241
+ "aria-describedby": describedBy ? `${describedBy} ${id}` : id
242
+ });
243
+ }, [children, id, isOpen, required]);
244
+ const isShown = required && isOpen && layout !== void 0;
167
245
  return /* @__PURE__ */ jsxs(
168
246
  "div",
169
247
  {
170
248
  ref: containerRef,
171
- className: `relative inline-flex ${containerClassName}`,
249
+ className: required ? `relative inline-flex ${containerClassName}` : "contents",
172
250
  onBlur: closeTooltip,
173
251
  onFocus: openTooltip,
174
252
  onMouseEnter: openTooltip,
175
253
  onMouseLeave: closeTooltip,
176
254
  children: [
177
- children,
178
- isOpen && tooltipStyle && createPortal(
255
+ trigger,
256
+ isShown && createPortal(
179
257
  /* @__PURE__ */ jsxs(
180
258
  "div",
181
259
  {
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) },
260
+ ref: tooltipRef,
261
+ id,
262
+ role: "tooltip",
263
+ 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} `,
264
+ style: {
265
+ ...layout.position,
266
+ "--tooltip-arrow-shift": `${String(layout.arrowShift)}px`,
267
+ transform: getTooltipTransform(layout.placement, isVisible)
268
+ },
184
269
  children: [
185
270
  /* @__PURE__ */ jsx("div", { className: "text-primary text-xs font-medium", children: title }),
186
271
  description && /* @__PURE__ */ jsx("div", { className: "text-tertiary text-[10px] font-light", children: description })
@@ -192,9 +277,6 @@ var OriginalTooltip = /* @__PURE__ */ __name(({
192
277
  ]
193
278
  }
194
279
  );
195
- }, "OriginalTooltip");
196
- var Tooltip = /* @__PURE__ */ __name(({ children, required = true, ...props }) => {
197
- return required ? /* @__PURE__ */ jsx(OriginalTooltip, { ...props, children }) : children;
198
280
  }, "Tooltip");
199
281
 
200
282
  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';\nimport { twMerge } from 'tailwind-merge';\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 // A class that is given wins over the one it clashes with (`rounded-md` over `rounded-lg`),\n // with no `!`; one for another screen size (`lg:px-5`) is a different class and stays.\n <button\n {...buttonProps}\n ref={ref}\n className={twMerge(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.1",
3
+ "version": "1.0.3",
4
4
  "description": "Shared React UI components for the Beautinique frontends.",
5
5
  "keywords": [
6
6
  "frontend",
@@ -79,6 +79,7 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "nanoid": "^6.0.1",
82
+ "tailwind-merge": "^3.7.0",
82
83
  "zustand": "^5.0.15"
83
84
  },
84
85
  "devDependencies": {