@lablup/ui-common 0.1.0-alpha.16 → 0.1.0-alpha.18

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.
@@ -13,6 +13,15 @@ export interface StatCardTrend {
13
13
  export interface StatCardProps {
14
14
  /** Short label rendered above the value (UPPERCASE styled) */
15
15
  label: string;
16
+ /**
17
+ * Rendered in place of `label` when the label is not plain text: a glossary
18
+ * term carrying its definition, a unit badge, an info affordance.
19
+ *
20
+ * `label` stays required and stays the accessible name, so the name is a
21
+ * string the consumer wrote rather than whatever text happens to fall out of
22
+ * a node. Pass the same words in both.
23
+ */
24
+ labelNode?: ReactNode;
16
25
  /** Primary metric value (number is coerced to localised string) */
17
26
  value: string | number;
18
27
  /** Optional suffix appended to the value (e.g. "/ 4", "GB") */
@@ -54,6 +63,6 @@ export interface StatCardProps {
54
63
  /** Visual weight. Default: "default" */
55
64
  emphasis?: StatCardEmphasis;
56
65
  }
57
- declare function StatCardComponent({ label, value, valueSuffix, hint, icon, tone, trend, onClick, loading, ariaLabel, className, testId, format, animate, sparkline, emphasis, }: StatCardProps): JSX.Element;
66
+ declare function StatCardComponent({ label, labelNode, value, valueSuffix, hint, icon, tone, trend, onClick, loading, ariaLabel, className, testId, format, animate, sparkline, emphasis, }: StatCardProps): JSX.Element;
58
67
  export declare const StatCard: import('react').MemoExoticComponent<typeof StatCardComponent>;
59
68
  export {};
@@ -30,76 +30,76 @@ var d = {
30
30
  function f(e, t) {
31
31
  return typeof e == "number" ? t ? t(e) : e.toLocaleString() : e;
32
32
  }
33
- function p({ label: r, value: i, valueSuffix: a, hint: o, icon: l, tone: p = "default", trend: m, onClick: h, loading: g = !1, ariaLabel: _, className: v = "", testId: y, format: b, animate: x = !1, sparkline: S, emphasis: C = "default" }) {
34
- let w = n(), T = typeof i == "number", E = x && T && !g && !w, D = u(T ? i : 0, E), O = f(T && E ? D : i, b), k = _ ?? (g ? r : `${r}: ${f(i, b)}${a ?? ""}`), A = [
33
+ function p({ label: r, labelNode: i, value: a, valueSuffix: o, hint: l, icon: p, tone: m = "default", trend: h, onClick: g, loading: _ = !1, ariaLabel: v, className: y = "", testId: b, format: x, animate: S = !1, sparkline: C, emphasis: w = "default" }) {
34
+ let T = n(), E = typeof a == "number", D = S && E && !_ && !T, O = u(E ? a : 0, D), k = f(E && D ? O : a, x), A = v ?? (_ ? r : `${r}: ${f(a, x)}${o ?? ""}`), j = [
35
35
  "stat-card",
36
36
  "corner-accent",
37
- `stat-card--tone-${p}`,
38
- C === "default" ? "" : `stat-card--${C}`,
39
- v
37
+ `stat-card--tone-${m}`,
38
+ w === "default" ? "" : `stat-card--${w}`,
39
+ y
40
40
  ].filter(Boolean).join(" ");
41
41
  return /* @__PURE__ */ c(e, {
42
- className: A,
43
- onClick: h,
44
- role: h ? void 0 : "group",
45
- ariaLabel: k,
46
- testId: y,
42
+ className: j,
43
+ onClick: g,
44
+ role: g ? void 0 : "group",
45
+ ariaLabel: A,
46
+ testId: b,
47
47
  children: [
48
48
  /* @__PURE__ */ c("div", {
49
49
  className: "stat-card__header",
50
50
  children: [/* @__PURE__ */ s("span", {
51
51
  className: "stat-card__label",
52
- children: r
53
- }), l && /* @__PURE__ */ s("span", {
52
+ children: i ?? r
53
+ }), p && /* @__PURE__ */ s("span", {
54
54
  className: "stat-card__icon",
55
55
  "aria-hidden": "true",
56
- children: l
56
+ children: p
57
57
  })]
58
58
  }),
59
59
  /* @__PURE__ */ c("div", {
60
60
  className: "stat-card__body",
61
- children: [g ? /* @__PURE__ */ s(t, {
61
+ children: [_ ? /* @__PURE__ */ s(t, {
62
62
  width: "60%",
63
63
  height: "2rem"
64
- }) : S ? /* @__PURE__ */ c("span", {
64
+ }) : C ? /* @__PURE__ */ c("span", {
65
65
  className: "stat-card__value-line",
66
66
  children: [/* @__PURE__ */ c("span", {
67
67
  className: "stat-card__value-row",
68
68
  children: [/* @__PURE__ */ s("span", {
69
69
  className: "stat-card__value",
70
- children: O
71
- }), a && /* @__PURE__ */ s("span", {
70
+ children: k
71
+ }), o && /* @__PURE__ */ s("span", {
72
72
  className: "stat-card__value-suffix",
73
- children: a
73
+ children: o
74
74
  })]
75
75
  }), /* @__PURE__ */ s("span", {
76
76
  className: "stat-card__sparkline",
77
- children: S
77
+ children: C
78
78
  })]
79
79
  }) : /* @__PURE__ */ c("span", {
80
80
  className: "stat-card__value-row",
81
81
  children: [/* @__PURE__ */ s("span", {
82
82
  className: "stat-card__value",
83
- children: O
84
- }), a && /* @__PURE__ */ s("span", {
83
+ children: k
84
+ }), o && /* @__PURE__ */ s("span", {
85
85
  className: "stat-card__value-suffix",
86
- children: a
86
+ children: o
87
87
  })]
88
- }), m && !g && /* @__PURE__ */ c("span", {
89
- className: `stat-card__trend stat-card__trend--${m.direction}`,
90
- "aria-label": m.ariaLabel ?? m.label,
88
+ }), h && !_ && /* @__PURE__ */ c("span", {
89
+ className: `stat-card__trend stat-card__trend--${h.direction}`,
90
+ "aria-label": h.ariaLabel ?? h.label,
91
91
  children: [/* @__PURE__ */ s("span", {
92
92
  "aria-hidden": "true",
93
- children: d[m.direction]
94
- }), /* @__PURE__ */ s("span", { children: m.label })]
93
+ children: d[h.direction]
94
+ }), /* @__PURE__ */ s("span", { children: h.label })]
95
95
  })]
96
96
  }),
97
- o != null && /* @__PURE__ */ s("div", {
97
+ l != null && /* @__PURE__ */ s("div", {
98
98
  className: "stat-card__hint",
99
- children: g ? /* @__PURE__ */ s(t, {
99
+ children: _ ? /* @__PURE__ */ s(t, {
100
100
  width: "80%",
101
101
  height: "0.85rem"
102
- }) : o
102
+ }) : l
103
103
  })
104
104
  ]
105
105
  });
@@ -1 +1 @@
1
- {"version":3,"file":"StatCard2.js","names":[],"sources":["../../../src/components/StatCard/StatCard.tsx"],"sourcesContent":["/**\n * StatCard Component\n *\n * Compact metric card for dashboards. Renders a label, a large numeric\n * value (with optional unit/suffix), and optional icon, hint, trend, or\n * accent tone. Wraps `BaseCard` so it inherits hover/focus/click affordances\n * and design tokens consistently with the rest of the common library.\n *\n * Designed for cross-page reuse: Squad dashboard, Statistics page, Cowork\n * dashboard, etc.\n */\n\nimport { memo, useEffect, useRef, useState, type ReactNode, type JSX } from \"react\";\nimport { usePrefersReducedMotion } from \"../../hooks/usePrefersReducedMotion\";\nimport { BaseCard } from \"../BaseCard\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./StatCard.css\";\n\nexport type StatCardEmphasis = \"compact\" | \"default\" | \"prominent\";\n\nexport type StatCardTone = \"default\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport type StatCardTrendDirection = \"up\" | \"down\" | \"flat\";\n\nexport interface StatCardTrend {\n /** Direction indicator (up / down / flat) */\n direction: StatCardTrendDirection;\n /** Human-readable delta label (e.g. \"+12%\", \"-3\", \"0\") */\n label: string;\n /** Optional ARIA description for screen readers */\n ariaLabel?: string;\n}\n\nexport interface StatCardProps {\n /** Short label rendered above the value (UPPERCASE styled) */\n label: string;\n /** Primary metric value (number is coerced to localised string) */\n value: string | number;\n /** Optional suffix appended to the value (e.g. \"/ 4\", \"GB\") */\n valueSuffix?: string;\n /** Optional helper line under the value (e.g. context, secondary metric) */\n hint?: ReactNode;\n /** Optional leading icon node, rendered inside an accent badge */\n icon?: ReactNode;\n /** Visual tone, mirrors Badge variants. Default: \"default\" */\n tone?: StatCardTone;\n /** Optional trend indicator under the value */\n trend?: StatCardTrend;\n /** Click handler. Renders the card as keyboard-accessible button. */\n onClick?: () => void;\n /** Render skeletons instead of value/label content while loading */\n loading?: boolean;\n /** Optional aria label override (defaults to label + value) */\n ariaLabel?: string;\n /** Extra class name applied to the root */\n className?: string;\n /** Test id for unit tests */\n testId?: string;\n /**\n * Formatter for numeric values (e.g. abbreviate to \"1.2K\", append \"%\").\n * Defaults to `toLocaleString`. Ignored when `value` is a string.\n */\n format?: (value: number) => string;\n /**\n * Count up to a numeric value on mount and on change. Off by default so\n * existing consumers render the final value immediately; opt in for\n * dashboard hero metrics. Suppressed under `prefers-reduced-motion`.\n */\n animate?: boolean;\n /**\n * Optional trailing visual on the value line (e.g. a sparkline). Kept as a\n * slot so the card does not depend on any particular chart component.\n */\n sparkline?: ReactNode;\n /** Visual weight. Default: \"default\" */\n emphasis?: StatCardEmphasis;\n}\n\nconst ANIMATION_DURATION_MS = 800;\n\n/**\n * Counts from the previous value to `target` with an ease-out-cubic curve.\n * When `enabled` is false the target is returned directly and no animation\n * frame is ever scheduled.\n */\nfunction useAnimatedValue(target: number, enabled: boolean): number {\n const [displayValue, setDisplayValue] = useState(enabled ? 0 : target);\n const previousValue = useRef(enabled ? 0 : target);\n const frameRef = useRef<number | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n previousValue.current = target;\n setDisplayValue(target);\n return;\n }\n\n const startValue = previousValue.current;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min((now - startTime) / ANIMATION_DURATION_MS, 1);\n const eased = 1 - Math.pow(1 - progress, 3);\n setDisplayValue(Math.round(startValue + (target - startValue) * eased));\n\n if (progress < 1) {\n frameRef.current = requestAnimationFrame(step);\n } else {\n previousValue.current = target;\n }\n };\n\n frameRef.current = requestAnimationFrame(step);\n\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n }\n };\n }, [target, enabled]);\n\n return enabled ? displayValue : target;\n}\n\nconst TREND_GLYPH: Record<StatCardTrendDirection, string> = {\n up: \"▲\",\n down: \"▼\",\n flat: \"•\",\n};\n\nfunction formatValue(\n value: string | number,\n format?: (value: number) => string,\n): string {\n if (typeof value === \"number\") {\n return format ? format(value) : value.toLocaleString();\n }\n return value;\n}\n\nfunction StatCardComponent({\n label,\n value,\n valueSuffix,\n hint,\n icon,\n tone = \"default\",\n trend,\n onClick,\n loading = false,\n ariaLabel,\n className = \"\",\n testId,\n format,\n animate = false,\n sparkline,\n emphasis = \"default\",\n}: StatCardProps): JSX.Element {\n const prefersReducedMotion = usePrefersReducedMotion();\n const isNumeric = typeof value === \"number\";\n const shouldAnimate = animate && isNumeric && !loading && !prefersReducedMotion;\n const animatedValue = useAnimatedValue(isNumeric ? value : 0, shouldAnimate);\n\n const displayValue = formatValue(\n isNumeric && shouldAnimate ? animatedValue : value,\n format,\n );\n\n // The aria-label always describes the settled value, never an in-flight\n // animation frame, so assistive tech is not read a counting-up number.\n const composedAriaLabel =\n ariaLabel ??\n (loading ? label : `${label}: ${formatValue(value, format)}${valueSuffix ?? \"\"}`);\n\n const rootClass = [\n \"stat-card\",\n \"corner-accent\",\n `stat-card--tone-${tone}`,\n emphasis !== \"default\" ? `stat-card--${emphasis}` : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <BaseCard\n className={rootClass}\n onClick={onClick}\n role={onClick ? undefined : \"group\"}\n ariaLabel={composedAriaLabel}\n testId={testId}\n >\n <div className=\"stat-card__header\">\n <span className=\"stat-card__label\">{label}</span>\n {icon && (\n <span className=\"stat-card__icon\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n </div>\n\n <div className=\"stat-card__body\">\n {loading ? (\n <Skeleton width=\"60%\" height=\"2rem\" />\n ) : sparkline ? (\n // Only wrap when a sparkline is present, so cards without one keep\n // the exact DOM they had before the slot existed.\n <span className=\"stat-card__value-line\">\n <span className=\"stat-card__value-row\">\n <span className=\"stat-card__value\">{displayValue}</span>\n {valueSuffix && (\n <span className=\"stat-card__value-suffix\">{valueSuffix}</span>\n )}\n </span>\n <span className=\"stat-card__sparkline\">{sparkline}</span>\n </span>\n ) : (\n <span className=\"stat-card__value-row\">\n <span className=\"stat-card__value\">{displayValue}</span>\n {valueSuffix && (\n <span className=\"stat-card__value-suffix\">{valueSuffix}</span>\n )}\n </span>\n )}\n\n {trend && !loading && (\n <span\n className={`stat-card__trend stat-card__trend--${trend.direction}`}\n aria-label={trend.ariaLabel ?? trend.label}\n >\n <span aria-hidden=\"true\">{TREND_GLYPH[trend.direction]}</span>\n <span>{trend.label}</span>\n </span>\n )}\n </div>\n\n {hint !== undefined && hint !== null && (\n <div className=\"stat-card__hint\">\n {loading ? <Skeleton width=\"80%\" height=\"0.85rem\" /> : hint}\n </div>\n )}\n </BaseCard>\n );\n}\n\nexport const StatCard = memo(StatCardComponent);\n"],"mappings":"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"}
1
+ {"version":3,"file":"StatCard2.js","names":[],"sources":["../../../src/components/StatCard/StatCard.tsx"],"sourcesContent":["/**\n * StatCard Component\n *\n * Compact metric card for dashboards. Renders a label, a large numeric\n * value (with optional unit/suffix), and optional icon, hint, trend, or\n * accent tone. The label can be a node (`labelNode`) while the accessible name\n * stays the plain `label` string. Wraps `BaseCard` so it inherits hover/focus/click affordances\n * and design tokens consistently with the rest of the common library.\n *\n * Designed for cross-page reuse: Squad dashboard, Statistics page, Cowork\n * dashboard, etc.\n */\n\nimport { memo, useEffect, useRef, useState, type ReactNode, type JSX } from \"react\";\nimport { usePrefersReducedMotion } from \"../../hooks/usePrefersReducedMotion\";\nimport { BaseCard } from \"../BaseCard\";\nimport { Skeleton } from \"../Skeleton\";\nimport \"./StatCard.css\";\n\nexport type StatCardEmphasis = \"compact\" | \"default\" | \"prominent\";\n\nexport type StatCardTone = \"default\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport type StatCardTrendDirection = \"up\" | \"down\" | \"flat\";\n\nexport interface StatCardTrend {\n /** Direction indicator (up / down / flat) */\n direction: StatCardTrendDirection;\n /** Human-readable delta label (e.g. \"+12%\", \"-3\", \"0\") */\n label: string;\n /** Optional ARIA description for screen readers */\n ariaLabel?: string;\n}\n\nexport interface StatCardProps {\n /** Short label rendered above the value (UPPERCASE styled) */\n label: string;\n /**\n * Rendered in place of `label` when the label is not plain text: a glossary\n * term carrying its definition, a unit badge, an info affordance.\n *\n * `label` stays required and stays the accessible name, so the name is a\n * string the consumer wrote rather than whatever text happens to fall out of\n * a node. Pass the same words in both.\n */\n labelNode?: ReactNode;\n /** Primary metric value (number is coerced to localised string) */\n value: string | number;\n /** Optional suffix appended to the value (e.g. \"/ 4\", \"GB\") */\n valueSuffix?: string;\n /** Optional helper line under the value (e.g. context, secondary metric) */\n hint?: ReactNode;\n /** Optional leading icon node, rendered inside an accent badge */\n icon?: ReactNode;\n /** Visual tone, mirrors Badge variants. Default: \"default\" */\n tone?: StatCardTone;\n /** Optional trend indicator under the value */\n trend?: StatCardTrend;\n /** Click handler. Renders the card as keyboard-accessible button. */\n onClick?: () => void;\n /** Render skeletons instead of value/label content while loading */\n loading?: boolean;\n /** Optional aria label override (defaults to label + value) */\n ariaLabel?: string;\n /** Extra class name applied to the root */\n className?: string;\n /** Test id for unit tests */\n testId?: string;\n /**\n * Formatter for numeric values (e.g. abbreviate to \"1.2K\", append \"%\").\n * Defaults to `toLocaleString`. Ignored when `value` is a string.\n */\n format?: (value: number) => string;\n /**\n * Count up to a numeric value on mount and on change. Off by default so\n * existing consumers render the final value immediately; opt in for\n * dashboard hero metrics. Suppressed under `prefers-reduced-motion`.\n */\n animate?: boolean;\n /**\n * Optional trailing visual on the value line (e.g. a sparkline). Kept as a\n * slot so the card does not depend on any particular chart component.\n */\n sparkline?: ReactNode;\n /** Visual weight. Default: \"default\" */\n emphasis?: StatCardEmphasis;\n}\n\nconst ANIMATION_DURATION_MS = 800;\n\n/**\n * Counts from the previous value to `target` with an ease-out-cubic curve.\n * When `enabled` is false the target is returned directly and no animation\n * frame is ever scheduled.\n */\nfunction useAnimatedValue(target: number, enabled: boolean): number {\n const [displayValue, setDisplayValue] = useState(enabled ? 0 : target);\n const previousValue = useRef(enabled ? 0 : target);\n const frameRef = useRef<number | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n previousValue.current = target;\n setDisplayValue(target);\n return;\n }\n\n const startValue = previousValue.current;\n const startTime = performance.now();\n\n const step = (now: number): void => {\n const progress = Math.min((now - startTime) / ANIMATION_DURATION_MS, 1);\n const eased = 1 - Math.pow(1 - progress, 3);\n setDisplayValue(Math.round(startValue + (target - startValue) * eased));\n\n if (progress < 1) {\n frameRef.current = requestAnimationFrame(step);\n } else {\n previousValue.current = target;\n }\n };\n\n frameRef.current = requestAnimationFrame(step);\n\n return () => {\n if (frameRef.current !== null) {\n cancelAnimationFrame(frameRef.current);\n }\n };\n }, [target, enabled]);\n\n return enabled ? displayValue : target;\n}\n\nconst TREND_GLYPH: Record<StatCardTrendDirection, string> = {\n up: \"▲\",\n down: \"▼\",\n flat: \"•\",\n};\n\nfunction formatValue(\n value: string | number,\n format?: (value: number) => string,\n): string {\n if (typeof value === \"number\") {\n return format ? format(value) : value.toLocaleString();\n }\n return value;\n}\n\nfunction StatCardComponent({\n label,\n labelNode,\n value,\n valueSuffix,\n hint,\n icon,\n tone = \"default\",\n trend,\n onClick,\n loading = false,\n ariaLabel,\n className = \"\",\n testId,\n format,\n animate = false,\n sparkline,\n emphasis = \"default\",\n}: StatCardProps): JSX.Element {\n const prefersReducedMotion = usePrefersReducedMotion();\n const isNumeric = typeof value === \"number\";\n const shouldAnimate = animate && isNumeric && !loading && !prefersReducedMotion;\n const animatedValue = useAnimatedValue(isNumeric ? value : 0, shouldAnimate);\n\n const displayValue = formatValue(\n isNumeric && shouldAnimate ? animatedValue : value,\n format,\n );\n\n // The aria-label always describes the settled value, never an in-flight\n // animation frame, so assistive tech is not read a counting-up number.\n const composedAriaLabel =\n ariaLabel ??\n (loading ? label : `${label}: ${formatValue(value, format)}${valueSuffix ?? \"\"}`);\n\n const rootClass = [\n \"stat-card\",\n \"corner-accent\",\n `stat-card--tone-${tone}`,\n emphasis !== \"default\" ? `stat-card--${emphasis}` : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <BaseCard\n className={rootClass}\n onClick={onClick}\n role={onClick ? undefined : \"group\"}\n ariaLabel={composedAriaLabel}\n testId={testId}\n >\n <div className=\"stat-card__header\">\n <span className=\"stat-card__label\">{labelNode ?? label}</span>\n {icon && (\n <span className=\"stat-card__icon\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n </div>\n\n <div className=\"stat-card__body\">\n {loading ? (\n <Skeleton width=\"60%\" height=\"2rem\" />\n ) : sparkline ? (\n // Only wrap when a sparkline is present, so cards without one keep\n // the exact DOM they had before the slot existed.\n <span className=\"stat-card__value-line\">\n <span className=\"stat-card__value-row\">\n <span className=\"stat-card__value\">{displayValue}</span>\n {valueSuffix && (\n <span className=\"stat-card__value-suffix\">{valueSuffix}</span>\n )}\n </span>\n <span className=\"stat-card__sparkline\">{sparkline}</span>\n </span>\n ) : (\n <span className=\"stat-card__value-row\">\n <span className=\"stat-card__value\">{displayValue}</span>\n {valueSuffix && (\n <span className=\"stat-card__value-suffix\">{valueSuffix}</span>\n )}\n </span>\n )}\n\n {trend && !loading && (\n <span\n className={`stat-card__trend stat-card__trend--${trend.direction}`}\n aria-label={trend.ariaLabel ?? trend.label}\n >\n <span aria-hidden=\"true\">{TREND_GLYPH[trend.direction]}</span>\n <span>{trend.label}</span>\n </span>\n )}\n </div>\n\n {hint !== undefined && hint !== null && (\n <div className=\"stat-card__hint\">\n {loading ? <Skeleton width=\"80%\" height=\"0.85rem\" /> : hint}\n </div>\n )}\n </BaseCard>\n );\n}\n\nexport const StatCard = memo(StatCardComponent);\n"],"mappings":"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"}
@@ -1,100 +1,98 @@
1
1
  /* empty css */
2
- import { useCallback as e, useEffect as t, useId as n, useRef as r, useState as i } from "react";
3
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
- import { createPortal as s } from "react-dom";
2
+ import { useCallback as e, useEffect as t, useId as n, useLayoutEffect as r, useRef as i, useState as a } from "react";
3
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
4
+ import { createPortal as c } from "react-dom";
5
5
  //#region src/components/Tooltip/Tooltip.tsx
6
- var c = 120;
7
- function l({ content: l, children: u, className: d, contentClassName: f, tooltipId: p, tabIndex: m = 0, toggleable: h = !1 }) {
8
- let [g, _] = i(!1), [v, y] = i(null), b = r(null), x = r(null), S = r(!0), C = r(null), w = n(), T = p ?? `tooltip-${w}`;
9
- t(() => (S.current = !0, () => {
10
- S.current = !1, C.current !== null && clearTimeout(C.current);
6
+ var l = 120;
7
+ function u({ content: u, children: d, className: f, contentClassName: p, tooltipId: m, tabIndex: h = 0, toggleable: g = !1 }) {
8
+ let [_, v] = a(!1), [y, b] = a(null), x = i(null), S = i(null), C = i(!0), w = i(null), T = n(), E = m ?? `tooltip-${T}`;
9
+ t(() => (C.current = !0, () => {
10
+ C.current = !1, w.current !== null && clearTimeout(w.current);
11
11
  }), []), t(() => {
12
- let e = x.current;
12
+ let e = S.current;
13
13
  if (!e) return;
14
14
  let t = () => {
15
- S.current && _(!0);
15
+ C.current && v(!0);
16
16
  }, n = () => {
17
- S.current && (_(!1), y(null));
17
+ C.current && (v(!1), b(null));
18
18
  };
19
19
  return e.addEventListener("focusin", t), e.addEventListener("focusout", n), () => {
20
20
  e.removeEventListener("focusin", t), e.removeEventListener("focusout", n);
21
21
  };
22
22
  }, []);
23
- let E = e(() => {
24
- let e = x.current, t = b.current;
25
- if (!S.current || !e || !t) return;
23
+ let D = e(() => {
24
+ let e = S.current, t = x.current;
25
+ if (!C.current || !e || !t) return;
26
26
  let n = e.getBoundingClientRect(), r = t.getBoundingClientRect(), i = n.top, a = window.innerHeight - n.bottom, o = r.height, s = i < o + 10 && a > i ? "bottom" : "top", c = n.left + n.width / 2, l = r.width, u = c - l / 2, d = window.innerWidth;
27
27
  u + l > d - 16 && (u = d - l - 16), u < 16 && (u = 16);
28
28
  let f = s === "top" ? n.top - o - 8 : n.bottom + 8;
29
- y({
29
+ b({
30
30
  top: f,
31
31
  left: u,
32
32
  placement: s
33
33
  });
34
34
  }, []);
35
- t(() => {
36
- if (!g) return;
37
- let e = requestAnimationFrame(E);
38
- return window.addEventListener("scroll", E, {
35
+ r(() => {
36
+ if (_) return D(), window.addEventListener("scroll", D, {
39
37
  capture: !0,
40
38
  passive: !0
41
- }), window.addEventListener("resize", E), () => {
42
- cancelAnimationFrame(e), window.removeEventListener("scroll", E, { capture: !0 }), window.removeEventListener("resize", E);
39
+ }), window.addEventListener("resize", D), () => {
40
+ window.removeEventListener("scroll", D, { capture: !0 }), window.removeEventListener("resize", D);
43
41
  };
44
- }, [g, E]);
45
- let D = e(() => {
46
- C.current !== null && (clearTimeout(C.current), C.current = null), _(!0);
47
- }, []), O = e(() => {
48
- C.current !== null && (clearTimeout(C.current), C.current = null), _(!1), y(null);
42
+ }, [_, D]);
43
+ let O = e(() => {
44
+ w.current !== null && (clearTimeout(w.current), w.current = null), v(!0);
49
45
  }, []), k = e(() => {
50
- C.current !== null && clearTimeout(C.current), C.current = setTimeout(() => {
51
- C.current = null, S.current && (_(!1), y(null));
52
- }, c);
46
+ w.current !== null && (clearTimeout(w.current), w.current = null), v(!1), b(null);
47
+ }, []), A = e(() => {
48
+ w.current !== null && clearTimeout(w.current), w.current = setTimeout(() => {
49
+ w.current = null, C.current && (v(!1), b(null));
50
+ }, l);
53
51
  }, []);
54
52
  t(() => {
55
- if (!g) return;
53
+ if (!_) return;
56
54
  let e = (e) => {
57
- e.key === "Escape" && O();
55
+ e.key === "Escape" && k();
58
56
  };
59
57
  return document.addEventListener("keydown", e), () => {
60
58
  document.removeEventListener("keydown", e);
61
59
  };
62
- }, [g, O]);
63
- let A = e((e) => {
64
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), g ? O() : D());
60
+ }, [_, k]);
61
+ let j = e((e) => {
62
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), _ ? k() : O());
65
63
  }, [
66
- g,
67
- O,
68
- D
69
- ]), j = g && /* @__PURE__ */ a("div", {
70
- ref: b,
71
- id: T,
72
- className: `tooltip__content tooltip__content--${v?.placement ?? "top"} ${f ?? ""}`,
73
- style: v ? {
74
- top: `${String(v.top)}px`,
75
- left: `${String(v.left)}px`
64
+ _,
65
+ k,
66
+ O
67
+ ]), M = _ && /* @__PURE__ */ o("div", {
68
+ ref: x,
69
+ id: E,
70
+ className: `tooltip__content tooltip__content--${y?.placement ?? "top"} ${p ?? ""}`,
71
+ style: y ? {
72
+ top: `${String(y.top)}px`,
73
+ left: `${String(y.left)}px`
76
74
  } : { visibility: "hidden" },
77
75
  role: "tooltip",
78
- children: typeof l == "string" ? /* @__PURE__ */ a("p", {
76
+ children: typeof u == "string" ? /* @__PURE__ */ o("p", {
79
77
  className: "tooltip__text",
80
- children: l
81
- }) : l
78
+ children: u
79
+ }) : u
82
80
  });
83
- return /* @__PURE__ */ o("div", {
84
- ref: x,
85
- className: `tooltip__wrapper ${d ?? ""}`,
86
- onMouseEnter: D,
87
- onMouseLeave: k,
88
- onKeyDown: h ? A : void 0,
89
- tabIndex: m,
90
- role: h ? "button" : void 0,
91
- "aria-expanded": h ? g : void 0,
92
- "aria-describedby": g ? T : void 0,
93
- children: [u, j && s(j, document.body)]
81
+ return /* @__PURE__ */ s("div", {
82
+ ref: S,
83
+ className: `tooltip__wrapper ${f ?? ""}`,
84
+ onMouseEnter: O,
85
+ onMouseLeave: A,
86
+ onKeyDown: g ? j : void 0,
87
+ tabIndex: h,
88
+ role: g ? "button" : void 0,
89
+ "aria-expanded": g ? _ : void 0,
90
+ "aria-describedby": _ ? E : void 0,
91
+ children: [d, M && c(M, document.body)]
94
92
  });
95
93
  }
96
94
  //#endregion
97
- export { l as Tooltip };
95
+ export { u as Tooltip };
98
96
 
99
97
  //# sourceMappingURL=Tooltip2.js.map
100
98
  import "../../assets/components/Tooltip/Tooltip.css";
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip2.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * Tooltip Component\n *\n * A reusable tooltip component that displays content on hover.\n * Supports both simple text and complex ReactNode content.\n *\n * Accessibility, against the three parts of WCAG 2.1 SC 1.4.13:\n * - Hoverable: the pointer can move onto the tooltip itself without it\n * disappearing. The content sits 8px away from the trigger, so the pointer\n * crosses a gap to get there and the hide is deferred over that crossing.\n * - Dismissible: Escape closes it without moving the pointer or focus.\n * - Persistent: it stays while the trigger is hovered or focused.\n *\n * Also uses role=\"tooltip\" and aria-describedby so a screen reader announces\n * the content with the trigger. `toggleable` additionally makes the trigger a\n * button with aria-expanded that Enter and Space operate, for content a reader\n * summons rather than passes over.\n */\n\nimport {\n useState,\n useRef,\n useEffect,\n useCallback,\n useId,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Tooltip.css\";\n\nexport interface TooltipProps {\n /** Tooltip content - can be string or ReactNode */\n content: ReactNode;\n /** Children to wrap with tooltip (trigger element) */\n children: ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** Additional class name for the tooltip content */\n contentClassName?: string;\n /** Custom ID for the tooltip (for aria-describedby association) */\n tooltipId?: string;\n /** Tab index for the wrapper. Use -1 when wrapping already-focusable elements like buttons. Defaults to 0. */\n tabIndex?: number;\n /**\n * Announce the trigger as a button carrying `aria-expanded`, and let Enter\n * and Space toggle the content.\n *\n * Off by default: a trigger whose content only supplements what is already\n * on screen is not a control and should not claim to be one. Turn it on\n * where the content is something a reader summons deliberately. A glossary\n * term is the case this exists for, and two products had independently built\n * the same trigger contract around one.\n *\n * Do not combine with `tabIndex={-1}`, which is for wrapping an element that\n * is already focusable and already owns its role.\n */\n toggleable?: boolean;\n}\n\n/** Long enough to cross the 8px gap to the tooltip, short enough not to linger. */\nconst HIDE_GRACE_MS = 120;\n\ninterface TooltipPosition {\n top: number;\n left: number;\n placement: \"top\" | \"bottom\";\n}\n\nexport function Tooltip({\n content,\n children,\n className,\n contentClassName,\n tooltipId,\n tabIndex = 0,\n toggleable = false,\n}: TooltipProps) {\n const [isVisible, setIsVisible] = useState(false);\n const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition | null>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const isMountedRef = useRef(true);\n const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const generatedId = useId();\n const effectiveTooltipId = tooltipId ?? `tooltip-${generatedId}`;\n\n // Track mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n };\n }, []);\n\n // Handle focus events using focusin/focusout which bubble from child elements\n // This ensures tooltips show when keyboard focus moves to children (WCAG 1.4.13)\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n\n // focusin/focusout bubble from child elements, unlike focus/blur\n const handleFocusIn = () => {\n if (isMountedRef.current) {\n setIsVisible(true);\n }\n };\n\n const handleFocusOut = () => {\n if (isMountedRef.current) {\n setIsVisible(false);\n setTooltipPosition(null);\n }\n };\n\n wrapper.addEventListener(\"focusin\", handleFocusIn);\n wrapper.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n wrapper.removeEventListener(\"focusin\", handleFocusIn);\n wrapper.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, []);\n\n /**\n * Place the tooltip beside the trigger, in whichever direction has room.\n *\n * Reads both boxes at call time rather than closing over them, because this\n * runs again every time the trigger moves under the viewport.\n */\n const measure = useCallback(() => {\n const wrapper = wrapperRef.current;\n const tooltip = tooltipRef.current;\n if (!isMountedRef.current || !wrapper || !tooltip) return;\n\n const wrapperRect = wrapper.getBoundingClientRect();\n const tooltipRect = tooltip.getBoundingClientRect();\n const spaceAbove = wrapperRect.top;\n const spaceBelow = window.innerHeight - wrapperRect.bottom;\n\n const tooltipHeight = tooltipRect.height;\n const margin = 10;\n\n // Determine placement\n const placement: \"top\" | \"bottom\" =\n spaceAbove < tooltipHeight + margin && spaceBelow > spaceAbove ? \"bottom\" : \"top\";\n\n // Calculate left position (center on wrapper, but ensure it stays within viewport)\n const wrapperCenter = wrapperRect.left + wrapperRect.width / 2;\n const tooltipWidth = tooltipRect.width;\n let left = wrapperCenter - tooltipWidth / 2;\n const viewportWidth = window.innerWidth;\n\n // Keep tooltip within viewport bounds\n if (left + tooltipWidth > viewportWidth - 16) {\n left = viewportWidth - tooltipWidth - 16;\n }\n if (left < 16) {\n left = 16;\n }\n\n // Calculate top position\n const top =\n placement === \"top\"\n ? wrapperRect.top - tooltipHeight - 8\n : wrapperRect.bottom + 8;\n\n setTooltipPosition({ top, left, placement });\n }, []);\n\n /**\n * The content is `position: fixed` and lives in a portal, so nothing moves it\n * when the trigger moves. Any scroll between the two, on the page or on a\n * container in between, leaves it pointing at where the trigger used to be,\n * and a resize can leave it off the edge. Scroll events from a container do\n * not bubble, hence the capture phase.\n *\n * The first measurement waits for a frame so the content has been laid out\n * and has a height to place against.\n */\n useEffect(() => {\n if (!isVisible) return;\n\n const frame = requestAnimationFrame(measure);\n window.addEventListener(\"scroll\", measure, { capture: true, passive: true });\n window.addEventListener(\"resize\", measure);\n\n return () => {\n cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", measure, { capture: true });\n window.removeEventListener(\"resize\", measure);\n };\n }, [isVisible, measure]);\n\n const handleShow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(true);\n }, []);\n\n const hideNow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(false);\n setTooltipPosition(null);\n }, []);\n\n /**\n * Deferred so the pointer can cross the 8px gap between the trigger and the\n * tooltip. React propagates enter and leave through the portal, so a direct\n * hop onto the content never fires this; the gap does, because the pointer\n * passes over a node that belongs to neither element. Hiding on that would\n * put the content out of reach (WCAG 2.1 SC 1.4.13, Hoverable). Arriving on\n * either element cancels the pending hide.\n */\n const handleHide = useCallback(() => {\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n hideTimerRef.current = setTimeout(() => {\n hideTimerRef.current = null;\n if (!isMountedRef.current) return;\n setIsVisible(false);\n setTooltipPosition(null);\n }, HIDE_GRACE_MS);\n }, []);\n\n // Dismissible: Escape closes without moving the pointer or focus. Bound to\n // the document because focus may sit on a child of the trigger, or nowhere\n // at all when the tooltip was opened by hover.\n useEffect(() => {\n if (!isVisible) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") hideNow();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [isVisible, hideNow]);\n\n /**\n * Enter and Space toggle, for a trigger that is a control rather than\n * incidental hover help. Both default to something else, Space scrolling the\n * page and Enter submitting an enclosing form, so both are prevented.\n */\n const handleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n if (isVisible) {\n hideNow();\n } else {\n handleShow();\n }\n },\n [isVisible, hideNow, handleShow],\n );\n\n const tooltipElement = isVisible && (\n <div\n ref={tooltipRef}\n id={effectiveTooltipId}\n className={`tooltip__content tooltip__content--${tooltipPosition?.placement ?? \"top\"} ${contentClassName ?? \"\"}`}\n style={\n tooltipPosition\n ? {\n top: `${String(tooltipPosition.top)}px`,\n left: `${String(tooltipPosition.left)}px`,\n }\n : { visibility: \"hidden\" }\n }\n role=\"tooltip\"\n >\n {typeof content === \"string\" ? (\n <p className=\"tooltip__text\">{content}</p>\n ) : (\n content\n )}\n </div>\n );\n\n return (\n <div\n ref={wrapperRef}\n className={`tooltip__wrapper ${className ?? \"\"}`}\n onMouseEnter={handleShow}\n onMouseLeave={handleHide}\n onKeyDown={toggleable ? handleKeyDown : undefined}\n tabIndex={tabIndex}\n role={toggleable ? \"button\" : undefined}\n aria-expanded={toggleable ? isVisible : undefined}\n aria-describedby={isVisible ? effectiveTooltipId : undefined}\n >\n {children}\n {tooltipElement && createPortal(tooltipElement, document.body)}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Tooltip2.js","names":[],"sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["/**\n * Tooltip Component\n *\n * A reusable tooltip component that displays content on hover.\n * Supports both simple text and complex ReactNode content.\n *\n * Accessibility, against the three parts of WCAG 2.1 SC 1.4.13:\n * - Hoverable: the pointer can move onto the tooltip itself without it\n * disappearing. The content sits 8px away from the trigger, so the pointer\n * crosses a gap to get there and the hide is deferred over that crossing.\n * - Dismissible: Escape closes it without moving the pointer or focus.\n * - Persistent: it stays while the trigger is hovered or focused.\n *\n * Also uses role=\"tooltip\" and aria-describedby so a screen reader announces\n * the content with the trigger. `toggleable` additionally makes the trigger a\n * button with aria-expanded that Enter and Space operate, for content a reader\n * summons rather than passes over.\n */\n\nimport {\n useState,\n useRef,\n useEffect,\n useLayoutEffect,\n useCallback,\n useId,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport \"./Tooltip.css\";\n\nexport interface TooltipProps {\n /** Tooltip content - can be string or ReactNode */\n content: ReactNode;\n /** Children to wrap with tooltip (trigger element) */\n children: ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** Additional class name for the tooltip content */\n contentClassName?: string;\n /** Custom ID for the tooltip (for aria-describedby association) */\n tooltipId?: string;\n /** Tab index for the wrapper. Use -1 when wrapping already-focusable elements like buttons. Defaults to 0. */\n tabIndex?: number;\n /**\n * Announce the trigger as a button carrying `aria-expanded`, and let Enter\n * and Space toggle the content.\n *\n * Off by default: a trigger whose content only supplements what is already\n * on screen is not a control and should not claim to be one. Turn it on\n * where the content is something a reader summons deliberately. A glossary\n * term is the case this exists for, and two products had independently built\n * the same trigger contract around one.\n *\n * Do not combine with `tabIndex={-1}`, which is for wrapping an element that\n * is already focusable and already owns its role.\n */\n toggleable?: boolean;\n}\n\n/** Long enough to cross the 8px gap to the tooltip, short enough not to linger. */\nconst HIDE_GRACE_MS = 120;\n\ninterface TooltipPosition {\n top: number;\n left: number;\n placement: \"top\" | \"bottom\";\n}\n\nexport function Tooltip({\n content,\n children,\n className,\n contentClassName,\n tooltipId,\n tabIndex = 0,\n toggleable = false,\n}: TooltipProps) {\n const [isVisible, setIsVisible] = useState(false);\n const [tooltipPosition, setTooltipPosition] = useState<TooltipPosition | null>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const isMountedRef = useRef(true);\n const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const generatedId = useId();\n const effectiveTooltipId = tooltipId ?? `tooltip-${generatedId}`;\n\n // Track mount state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n };\n }, []);\n\n // Handle focus events using focusin/focusout which bubble from child elements\n // This ensures tooltips show when keyboard focus moves to children (WCAG 1.4.13)\n useEffect(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n\n // focusin/focusout bubble from child elements, unlike focus/blur\n const handleFocusIn = () => {\n if (isMountedRef.current) {\n setIsVisible(true);\n }\n };\n\n const handleFocusOut = () => {\n if (isMountedRef.current) {\n setIsVisible(false);\n setTooltipPosition(null);\n }\n };\n\n wrapper.addEventListener(\"focusin\", handleFocusIn);\n wrapper.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n wrapper.removeEventListener(\"focusin\", handleFocusIn);\n wrapper.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, []);\n\n /**\n * Place the tooltip beside the trigger, in whichever direction has room.\n *\n * Reads both boxes at call time rather than closing over them, because this\n * runs again every time the trigger moves under the viewport.\n */\n const measure = useCallback(() => {\n const wrapper = wrapperRef.current;\n const tooltip = tooltipRef.current;\n if (!isMountedRef.current || !wrapper || !tooltip) return;\n\n const wrapperRect = wrapper.getBoundingClientRect();\n const tooltipRect = tooltip.getBoundingClientRect();\n const spaceAbove = wrapperRect.top;\n const spaceBelow = window.innerHeight - wrapperRect.bottom;\n\n const tooltipHeight = tooltipRect.height;\n const margin = 10;\n\n // Determine placement\n const placement: \"top\" | \"bottom\" =\n spaceAbove < tooltipHeight + margin && spaceBelow > spaceAbove ? \"bottom\" : \"top\";\n\n // Calculate left position (center on wrapper, but ensure it stays within viewport)\n const wrapperCenter = wrapperRect.left + wrapperRect.width / 2;\n const tooltipWidth = tooltipRect.width;\n let left = wrapperCenter - tooltipWidth / 2;\n const viewportWidth = window.innerWidth;\n\n // Keep tooltip within viewport bounds\n if (left + tooltipWidth > viewportWidth - 16) {\n left = viewportWidth - tooltipWidth - 16;\n }\n if (left < 16) {\n left = 16;\n }\n\n // Calculate top position\n const top =\n placement === \"top\"\n ? wrapperRect.top - tooltipHeight - 8\n : wrapperRect.bottom + 8;\n\n setTooltipPosition({ top, left, placement });\n }, []);\n\n /**\n * The content is `position: fixed` and lives in a portal, so nothing moves it\n * when the trigger moves. Any scroll between the two, on the page or on a\n * container in between, leaves it pointing at where the trigger used to be,\n * and a resize can leave it off the edge. Scroll events from a container do\n * not bubble, hence the capture phase.\n *\n * A layout effect, not an effect plus a frame. The content is in the DOM by\n * the time this runs and can be measured, and the state it sets is flushed\n * before the browser paints, so the tooltip is never painted unplaced. The\n * frame it used to wait for was a frame the tooltip spent mounted and\n * `visibility: hidden`, which is out of the accessibility tree: present but\n * unreadable, and unfindable by any query that respects that tree.\n */\n useLayoutEffect(() => {\n if (!isVisible) return;\n\n measure();\n window.addEventListener(\"scroll\", measure, { capture: true, passive: true });\n window.addEventListener(\"resize\", measure);\n\n return () => {\n window.removeEventListener(\"scroll\", measure, { capture: true });\n window.removeEventListener(\"resize\", measure);\n };\n }, [isVisible, measure]);\n\n const handleShow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(true);\n }, []);\n\n const hideNow = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n setIsVisible(false);\n setTooltipPosition(null);\n }, []);\n\n /**\n * Deferred so the pointer can cross the 8px gap between the trigger and the\n * tooltip. React propagates enter and leave through the portal, so a direct\n * hop onto the content never fires this; the gap does, because the pointer\n * passes over a node that belongs to neither element. Hiding on that would\n * put the content out of reach (WCAG 2.1 SC 1.4.13, Hoverable). Arriving on\n * either element cancels the pending hide.\n */\n const handleHide = useCallback(() => {\n if (hideTimerRef.current !== null) clearTimeout(hideTimerRef.current);\n hideTimerRef.current = setTimeout(() => {\n hideTimerRef.current = null;\n if (!isMountedRef.current) return;\n setIsVisible(false);\n setTooltipPosition(null);\n }, HIDE_GRACE_MS);\n }, []);\n\n // Dismissible: Escape closes without moving the pointer or focus. Bound to\n // the document because focus may sit on a child of the trigger, or nowhere\n // at all when the tooltip was opened by hover.\n useEffect(() => {\n if (!isVisible) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") hideNow();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [isVisible, hideNow]);\n\n /**\n * Enter and Space toggle, for a trigger that is a control rather than\n * incidental hover help. Both default to something else, Space scrolling the\n * page and Enter submitting an enclosing form, so both are prevented.\n */\n const handleKeyDown = useCallback(\n (e: ReactKeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n if (isVisible) {\n hideNow();\n } else {\n handleShow();\n }\n },\n [isVisible, hideNow, handleShow],\n );\n\n const tooltipElement = isVisible && (\n <div\n ref={tooltipRef}\n id={effectiveTooltipId}\n className={`tooltip__content tooltip__content--${tooltipPosition?.placement ?? \"top\"} ${contentClassName ?? \"\"}`}\n style={\n tooltipPosition\n ? {\n top: `${String(tooltipPosition.top)}px`,\n left: `${String(tooltipPosition.left)}px`,\n }\n : { visibility: \"hidden\" }\n }\n role=\"tooltip\"\n >\n {typeof content === \"string\" ? (\n <p className=\"tooltip__text\">{content}</p>\n ) : (\n content\n )}\n </div>\n );\n\n return (\n <div\n ref={wrapperRef}\n className={`tooltip__wrapper ${className ?? \"\"}`}\n onMouseEnter={handleShow}\n onMouseLeave={handleHide}\n onKeyDown={toggleable ? handleKeyDown : undefined}\n tabIndex={tabIndex}\n role={toggleable ? \"button\" : undefined}\n aria-expanded={toggleable ? isVisible : undefined}\n aria-describedby={isVisible ? effectiveTooltipId : undefined}\n >\n {children}\n {tooltipElement && createPortal(tooltipElement, document.body)}\n </div>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lablup/ui-common",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "description": "Shared, product-neutral UI components and design tokens for Lablup products",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Lablup Inc.",