@hadoku/task-ui-components 0.0.0-stage → 4.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +917 -2
- package/dist/app-header.css +288 -0
- package/dist/bento.css +143 -0
- package/dist/button.css +227 -0
- package/dist/components/AppHeader.d.ts +61 -0
- package/dist/components/AppHeader.d.ts.map +1 -0
- package/dist/components/AppHeader.js +23 -0
- package/dist/components/AppHeader.js.map +1 -0
- package/dist/components/BentoCard.d.ts +23 -0
- package/dist/components/BentoCard.d.ts.map +1 -0
- package/dist/components/BentoCard.js +49 -0
- package/dist/components/BentoCard.js.map +1 -0
- package/dist/components/BentoGrid.d.ts +24 -0
- package/dist/components/BentoGrid.d.ts.map +1 -0
- package/dist/components/BentoGrid.js +22 -0
- package/dist/components/BentoGrid.js.map +1 -0
- package/dist/components/Button.d.ts +28 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +51 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/ConnectedSettings.d.ts +53 -0
- package/dist/components/ConnectedSettings.d.ts.map +1 -0
- package/dist/components/ConnectedSettings.js +368 -0
- package/dist/components/ConnectedSettings.js.map +1 -0
- package/dist/components/ConnectedThemePicker.d.ts +52 -0
- package/dist/components/ConnectedThemePicker.d.ts.map +1 -0
- package/dist/components/ConnectedThemePicker.js +32 -0
- package/dist/components/ConnectedThemePicker.js.map +1 -0
- package/dist/components/ContextMenu.d.ts +22 -0
- package/dist/components/ContextMenu.d.ts.map +1 -0
- package/dist/components/ContextMenu.js +23 -0
- package/dist/components/ContextMenu.js.map +1 -0
- package/dist/components/HadokuThemePicker.d.ts +27 -0
- package/dist/components/HadokuThemePicker.d.ts.map +1 -0
- package/dist/components/HadokuThemePicker.js +19 -0
- package/dist/components/HadokuThemePicker.js.map +1 -0
- package/dist/components/LoadingSkeleton.d.ts +14 -0
- package/dist/components/LoadingSkeleton.d.ts.map +1 -0
- package/dist/components/LoadingSkeleton.js +62 -0
- package/dist/components/LoadingSkeleton.js.map +1 -0
- package/dist/components/Modal.d.ts +39 -0
- package/dist/components/Modal.d.ts.map +1 -0
- package/dist/components/Modal.js +106 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/NumberInput.d.ts +26 -0
- package/dist/components/NumberInput.d.ts.map +1 -0
- package/dist/components/NumberInput.js +131 -0
- package/dist/components/NumberInput.js.map +1 -0
- package/dist/components/Select.d.ts +52 -0
- package/dist/components/Select.d.ts.map +1 -0
- package/dist/components/Select.js +281 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SettingsModal.d.ts +69 -0
- package/dist/components/SettingsModal.d.ts.map +1 -0
- package/dist/components/SettingsModal.js +141 -0
- package/dist/components/SettingsModal.js.map +1 -0
- package/dist/components/ThemeIcons.d.ts +26 -0
- package/dist/components/ThemeIcons.d.ts.map +1 -0
- package/dist/components/ThemeIcons.js +74 -0
- package/dist/components/ThemeIcons.js.map +1 -0
- package/dist/components/ThemePicker.d.ts +28 -0
- package/dist/components/ThemePicker.d.ts.map +1 -0
- package/dist/components/ThemePicker.js +101 -0
- package/dist/components/ThemePicker.js.map +1 -0
- package/dist/components/Toast.d.ts +13 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +70 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Toaster.d.ts +19 -0
- package/dist/components/Toaster.d.ts.map +1 -0
- package/dist/components/Toaster.js +19 -0
- package/dist/components/Toaster.js.map +1 -0
- package/dist/components/Tooltip.d.ts +49 -0
- package/dist/components/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip.js +143 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/context-menu.css +11 -0
- package/dist/controls.css +137 -0
- package/dist/hooks/useReducedMotion.d.ts +7 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/hooks/useReducedMotion.js +25 -0
- package/dist/hooks/useReducedMotion.js.map +1 -0
- package/dist/hooks/useToast.d.ts +18 -0
- package/dist/hooks/useToast.d.ts.map +1 -0
- package/dist/hooks/useToast.js +28 -0
- package/dist/hooks/useToast.js.map +1 -0
- package/dist/icon-btn.css +64 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/menuPlacement.d.ts +16 -0
- package/dist/lib/menuPlacement.d.ts.map +1 -0
- package/dist/lib/menuPlacement.js +19 -0
- package/dist/lib/menuPlacement.js.map +1 -0
- package/dist/lib/selectMenus.d.ts +30 -0
- package/dist/lib/selectMenus.d.ts.map +1 -0
- package/dist/lib/selectMenus.js +236 -0
- package/dist/lib/selectMenus.js.map +1 -0
- package/dist/lib/settingsClient.d.ts +144 -0
- package/dist/lib/settingsClient.d.ts.map +1 -0
- package/dist/lib/settingsClient.js +157 -0
- package/dist/lib/settingsClient.js.map +1 -0
- package/dist/lib/themeScope.d.ts +19 -0
- package/dist/lib/themeScope.d.ts.map +1 -0
- package/dist/lib/themeScope.js +10 -0
- package/dist/lib/themeScope.js.map +1 -0
- package/dist/lib/titleTooltips.d.ts +38 -0
- package/dist/lib/titleTooltips.d.ts.map +1 -0
- package/dist/lib/titleTooltips.js +166 -0
- package/dist/lib/titleTooltips.js.map +1 -0
- package/dist/lib/tooltipPlacement.d.ts +14 -0
- package/dist/lib/tooltipPlacement.d.ts.map +1 -0
- package/dist/lib/tooltipPlacement.js +34 -0
- package/dist/lib/tooltipPlacement.js.map +1 -0
- package/dist/modal.css +129 -0
- package/dist/number-input.css +131 -0
- package/dist/scrollbar.css +165 -0
- package/dist/select.css +251 -0
- package/dist/styles.css +1866 -0
- package/dist/task-ui-components.css +1866 -0
- package/dist/theme-picker.css +219 -0
- package/dist/toaster.css +159 -0
- package/dist/tooltip.css +103 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +60 -4
- package/src/app-header.css +288 -0
- package/src/bento.css +143 -0
- package/src/button.css +227 -0
- package/src/components/AppHeader.tsx +87 -0
- package/src/components/BentoCard.tsx +72 -0
- package/src/components/BentoGrid.tsx +48 -0
- package/src/components/Button.tsx +81 -0
- package/src/components/ConnectedSettings.tsx +545 -0
- package/src/components/ConnectedThemePicker.tsx +79 -0
- package/src/components/ContextMenu.tsx +48 -0
- package/src/components/HadokuThemePicker.tsx +39 -0
- package/src/components/LoadingSkeleton.tsx +84 -0
- package/src/components/Modal.tsx +171 -0
- package/src/components/NumberInput.tsx +161 -0
- package/src/components/Select.tsx +359 -0
- package/src/components/SettingsModal.tsx +235 -0
- package/src/components/ThemeIcons.tsx +127 -0
- package/src/components/ThemePicker.tsx +149 -0
- package/src/components/Toast.tsx +92 -0
- package/src/components/Toaster.tsx +43 -0
- package/src/components/Tooltip.tsx +212 -0
- package/src/context-menu.css +11 -0
- package/src/controls.css +137 -0
- package/src/hooks/useReducedMotion.ts +36 -0
- package/src/hooks/useToast.ts +57 -0
- package/src/icon-btn.css +64 -0
- package/src/index.ts +153 -0
- package/src/lib/menuPlacement.ts +31 -0
- package/src/lib/selectMenus.ts +303 -0
- package/src/lib/settingsClient.ts +425 -0
- package/src/lib/themeScope.ts +23 -0
- package/src/lib/titleTooltips.ts +236 -0
- package/src/lib/tooltipPlacement.ts +59 -0
- package/src/modal.css +129 -0
- package/src/number-input.css +131 -0
- package/src/scrollbar.css +165 -0
- package/src/select.css +251 -0
- package/src/theme-picker.css +219 -0
- package/src/toaster.css +159 -0
- package/src/tooltip.css +103 -0
- package/src/types/index.ts +43 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
const iconProps = {
|
|
3
|
+
width: 20,
|
|
4
|
+
height: 20,
|
|
5
|
+
viewBox: "0 0 24 24",
|
|
6
|
+
fill: "none",
|
|
7
|
+
stroke: "currentColor",
|
|
8
|
+
strokeWidth: 2,
|
|
9
|
+
strokeLinecap: "round",
|
|
10
|
+
strokeLinejoin: "round"
|
|
11
|
+
};
|
|
12
|
+
const SettingsIcon = () => /* @__PURE__ */ jsxs("svg", { ...iconProps, children: [
|
|
13
|
+
/* @__PURE__ */ jsx("rect", { x: "11", y: "1", width: "2", height: "3", fill: "currentColor" }),
|
|
14
|
+
/* @__PURE__ */ jsx("rect", { x: "16.5", y: "3.5", width: "3", height: "2", fill: "currentColor", transform: "rotate(45 18 4.5)" }),
|
|
15
|
+
/* @__PURE__ */ jsx("rect", { x: "19", y: "11", width: "3", height: "2", fill: "currentColor" }),
|
|
16
|
+
/* @__PURE__ */ jsx(
|
|
17
|
+
"rect",
|
|
18
|
+
{
|
|
19
|
+
x: "16.5",
|
|
20
|
+
y: "18.5",
|
|
21
|
+
width: "3",
|
|
22
|
+
height: "2",
|
|
23
|
+
fill: "currentColor",
|
|
24
|
+
transform: "rotate(-45 18 19.5)"
|
|
25
|
+
}
|
|
26
|
+
),
|
|
27
|
+
/* @__PURE__ */ jsx("rect", { x: "11", y: "20", width: "2", height: "3", fill: "currentColor" }),
|
|
28
|
+
/* @__PURE__ */ jsx("rect", { x: "4.5", y: "18.5", width: "3", height: "2", fill: "currentColor", transform: "rotate(45 6 19.5)" }),
|
|
29
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "11", width: "3", height: "2", fill: "currentColor" }),
|
|
30
|
+
/* @__PURE__ */ jsx("rect", { x: "4.5", y: "3.5", width: "3", height: "2", fill: "currentColor", transform: "rotate(-45 6 4.5)" }),
|
|
31
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "7", fill: "currentColor" }),
|
|
32
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4", fill: "var(--color-bg-card)" })
|
|
33
|
+
] });
|
|
34
|
+
const TagIcon = () => /* @__PURE__ */ jsxs("svg", { ...iconProps, width: 16, height: 16, viewBox: "0 0 20 20", children: [
|
|
35
|
+
/* @__PURE__ */ jsx("path", { d: "M2 4 L12 4 L16 10 L12 16 L2 16 Z", fill: "currentColor" }),
|
|
36
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "10", r: "1.5", fill: "white" })
|
|
37
|
+
] });
|
|
38
|
+
const CircleIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "8" }) });
|
|
39
|
+
const SquareIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "14", height: "14", rx: "2" }) });
|
|
40
|
+
const TriangleIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("path", { d: "M12 5 L21 19 L3 19 Z" }) });
|
|
41
|
+
const DiamondIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("path", { d: "M12 2 L22 12 L12 22 L2 12 Z" }) });
|
|
42
|
+
const StarIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("polygon", { points: "12,2 15,10 23,10 17,15 19,23 12,18 5,23 7,15 1,10 9,10" }) });
|
|
43
|
+
const HexagonIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("path", { d: "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" }) });
|
|
44
|
+
const PentagonIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("path", { d: "M12 2 L22 9 L18 20 L6 20 L2 9 Z" }) });
|
|
45
|
+
const OctagonIcon = () => /* @__PURE__ */ jsx("svg", { ...iconProps, children: /* @__PURE__ */ jsx("polygon", { points: "7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86" }) });
|
|
46
|
+
const FALLBACK_ICONS = [
|
|
47
|
+
CircleIcon,
|
|
48
|
+
SquareIcon,
|
|
49
|
+
TriangleIcon,
|
|
50
|
+
DiamondIcon,
|
|
51
|
+
StarIcon,
|
|
52
|
+
HexagonIcon,
|
|
53
|
+
PentagonIcon,
|
|
54
|
+
OctagonIcon
|
|
55
|
+
];
|
|
56
|
+
function getFallbackIcon(index) {
|
|
57
|
+
const Icon = FALLBACK_ICONS[index % FALLBACK_ICONS.length];
|
|
58
|
+
return /* @__PURE__ */ jsx(Icon, {});
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
CircleIcon,
|
|
62
|
+
DiamondIcon,
|
|
63
|
+
FALLBACK_ICONS,
|
|
64
|
+
HexagonIcon,
|
|
65
|
+
OctagonIcon,
|
|
66
|
+
PentagonIcon,
|
|
67
|
+
SettingsIcon,
|
|
68
|
+
SquareIcon,
|
|
69
|
+
StarIcon,
|
|
70
|
+
TagIcon,
|
|
71
|
+
TriangleIcon,
|
|
72
|
+
getFallbackIcon
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=ThemeIcons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeIcons.js","sources":["../../src/components/ThemeIcons.tsx"],"sourcesContent":["/**\n * Component icons - Simple monochrome SVG icons for theme picker\n * All icons use currentColor so they can be styled with CSS\n */\n\nimport React from 'react'\n\nconst iconProps = {\n width: 20,\n height: 20,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const\n}\n\nexport const SettingsIcon = () => (\n <svg {...iconProps}>\n {/* Gear teeth as rectangles */}\n <rect x=\"11\" y=\"1\" width=\"2\" height=\"3\" fill=\"currentColor\" />\n <rect x=\"16.5\" y=\"3.5\" width=\"3\" height=\"2\" fill=\"currentColor\" transform=\"rotate(45 18 4.5)\" />\n <rect x=\"19\" y=\"11\" width=\"3\" height=\"2\" fill=\"currentColor\" />\n <rect\n x=\"16.5\"\n y=\"18.5\"\n width=\"3\"\n height=\"2\"\n fill=\"currentColor\"\n transform=\"rotate(-45 18 19.5)\"\n />\n <rect x=\"11\" y=\"20\" width=\"2\" height=\"3\" fill=\"currentColor\" />\n <rect x=\"4.5\" y=\"18.5\" width=\"3\" height=\"2\" fill=\"currentColor\" transform=\"rotate(45 6 19.5)\" />\n <rect x=\"2\" y=\"11\" width=\"3\" height=\"2\" fill=\"currentColor\" />\n <rect x=\"4.5\" y=\"3.5\" width=\"3\" height=\"2\" fill=\"currentColor\" transform=\"rotate(-45 6 4.5)\" />\n\n {/* Outer ring */}\n <circle cx=\"12\" cy=\"12\" r=\"7\" fill=\"currentColor\" />\n\n {/* Center hole */}\n <circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"var(--color-bg-card)\" />\n </svg>\n)\n\nexport const TagIcon = () => (\n <svg {...iconProps} width={16} height={16} viewBox=\"0 0 20 20\">\n {/* Tag body - rectangular shape pointing right */}\n <path d=\"M2 4 L12 4 L16 10 L12 16 L2 16 Z\" fill=\"currentColor\" />\n {/* Hole in tag */}\n <circle cx=\"6\" cy=\"10\" r=\"1.5\" fill=\"white\" />\n </svg>\n)\n\nexport const CircleIcon = () => (\n <svg {...iconProps}>\n <circle cx=\"12\" cy=\"12\" r=\"8\" />\n </svg>\n)\n\nexport const SquareIcon = () => (\n <svg {...iconProps}>\n <rect x=\"5\" y=\"5\" width=\"14\" height=\"14\" rx=\"2\" />\n </svg>\n)\n\nexport const TriangleIcon = () => (\n <svg {...iconProps}>\n <path d=\"M12 5 L21 19 L3 19 Z\" />\n </svg>\n)\n\nexport const DiamondIcon = () => (\n <svg {...iconProps}>\n <path d=\"M12 2 L22 12 L12 22 L2 12 Z\" />\n </svg>\n)\n\nexport const StarIcon = () => (\n <svg {...iconProps}>\n <polygon points=\"12,2 15,10 23,10 17,15 19,23 12,18 5,23 7,15 1,10 9,10\" />\n </svg>\n)\n\nexport const HexagonIcon = () => (\n <svg {...iconProps}>\n <path d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\" />\n </svg>\n)\n\nexport const PentagonIcon = () => (\n <svg {...iconProps}>\n <path d=\"M12 2 L22 9 L18 20 L6 20 L2 9 Z\" />\n </svg>\n)\n\nexport const OctagonIcon = () => (\n <svg {...iconProps}>\n <polygon points=\"7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86\" />\n </svg>\n)\n\n/**\n * Fallback icon sets for themes - provides 8 distinct shapes\n * Use these when you don't have specific icons for your themes\n */\n\nexport const FALLBACK_ICONS = [\n CircleIcon,\n SquareIcon,\n TriangleIcon,\n DiamondIcon,\n StarIcon,\n HexagonIcon,\n PentagonIcon,\n OctagonIcon\n] as const\n\n/**\n * Get a fallback icon for a theme by index\n * Icons repeat after 8 themes to provide consistent differentiation\n */\n\nexport function getFallbackIcon(index: number) {\n const Icon = FALLBACK_ICONS[index % FALLBACK_ICONS.length]\n return <Icon />\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThemePicker component
|
|
3
|
+
* A beautiful theme picker with light/dark variants for each theme family
|
|
4
|
+
* Designed to work with any theme system via configuration
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { ThemePickerProps } from '../types';
|
|
8
|
+
import '../theme-picker.css';
|
|
9
|
+
/**
|
|
10
|
+
* ThemePicker - Dropdown theme selector with optional settings button
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { ThemePicker } from '@wolffm/task-ui-components'
|
|
15
|
+
* import '@wolffm/task-ui-components/theme-picker.css'
|
|
16
|
+
*
|
|
17
|
+
* <ThemePicker
|
|
18
|
+
* currentTheme="dark"
|
|
19
|
+
* isOpen={showPicker}
|
|
20
|
+
* themeFamilies={MY_THEME_FAMILIES}
|
|
21
|
+
* onThemeChange={setTheme}
|
|
22
|
+
* onToggle={() => setShowPicker(!showPicker)}
|
|
23
|
+
* onSettingsClick={() => openSettings()}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function ThemePicker({ currentTheme, isOpen, themeFamilies, onThemeChange, onToggle, onSettingsClick, getThemeIcon, className, dropdownPlacement }: ThemePickerProps): React.JSX.Element;
|
|
28
|
+
//# sourceMappingURL=ThemePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemePicker.d.ts","sourceRoot":"","sources":["../../src/components/ThemePicker.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAA4C,MAAM,OAAO,CAAA;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAIhD,OAAO,qBAAqB,CAAA;AAE5B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,YAAY,EACZ,MAAM,EACN,aAAa,EACb,aAAa,EACb,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,SAAc,EACd,iBAA0B,EAC3B,EAAE,gBAAgB,qBA0GlB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useLayoutEffect } from "react";
|
|
3
|
+
import { Tooltip } from "./Tooltip.js";
|
|
4
|
+
import { getFallbackIcon, SettingsIcon } from "./ThemeIcons.js";
|
|
5
|
+
import { MoonIcon } from "@wolffm/themes";
|
|
6
|
+
/* empty css */
|
|
7
|
+
function ThemePicker({
|
|
8
|
+
currentTheme,
|
|
9
|
+
isOpen,
|
|
10
|
+
themeFamilies,
|
|
11
|
+
onThemeChange,
|
|
12
|
+
onToggle,
|
|
13
|
+
onSettingsClick,
|
|
14
|
+
getThemeIcon,
|
|
15
|
+
className = "",
|
|
16
|
+
dropdownPlacement = "auto"
|
|
17
|
+
}) {
|
|
18
|
+
const dropdownRef = useRef(null);
|
|
19
|
+
const [resolvedPlacement, setResolvedPlacement] = useState("right");
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
if (!isOpen) return;
|
|
22
|
+
if (dropdownPlacement !== "auto") {
|
|
23
|
+
setResolvedPlacement(dropdownPlacement);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setResolvedPlacement("right");
|
|
27
|
+
const frameId = requestAnimationFrame(() => {
|
|
28
|
+
const el = dropdownRef.current;
|
|
29
|
+
if (!el) return;
|
|
30
|
+
const rect = el.getBoundingClientRect();
|
|
31
|
+
if (rect.right > window.innerWidth) {
|
|
32
|
+
setResolvedPlacement("left");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return () => cancelAnimationFrame(frameId);
|
|
36
|
+
}, [isOpen, dropdownPlacement]);
|
|
37
|
+
const currentIcon = getThemeIcon ? getThemeIcon(currentTheme) : /* @__PURE__ */ jsx(MoonIcon, {});
|
|
38
|
+
const openLeftClass = resolvedPlacement === "left" ? "theme-picker--open-left" : "";
|
|
39
|
+
return /* @__PURE__ */ jsxs("div", { className: `theme-picker ${openLeftClass} ${className}`.trim(), children: [
|
|
40
|
+
/* @__PURE__ */ jsx(Tooltip, { content: "Choose theme", children: /* @__PURE__ */ jsx(
|
|
41
|
+
"button",
|
|
42
|
+
{
|
|
43
|
+
className: "app-header__icon-btn theme-toggle-btn",
|
|
44
|
+
onClick: onToggle,
|
|
45
|
+
"aria-label": "Choose theme",
|
|
46
|
+
children: currentIcon
|
|
47
|
+
}
|
|
48
|
+
) }),
|
|
49
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
50
|
+
"div",
|
|
51
|
+
{
|
|
52
|
+
ref: dropdownRef,
|
|
53
|
+
className: "theme-picker__dropdown",
|
|
54
|
+
onClick: (e) => e.stopPropagation(),
|
|
55
|
+
children: /* @__PURE__ */ jsxs("div", { className: "theme-picker__main", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("div", { className: "theme-picker__pills", children: themeFamilies.map((family, idx) => {
|
|
57
|
+
const lightIcon = family.lightIcon ?? getFallbackIcon(idx);
|
|
58
|
+
const darkIcon = family.darkIcon ?? getFallbackIcon(idx);
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", { className: "theme-pill", children: [
|
|
60
|
+
/* @__PURE__ */ jsx(Tooltip, { content: family.lightLabel, children: /* @__PURE__ */ jsx(
|
|
61
|
+
"button",
|
|
62
|
+
{
|
|
63
|
+
className: `theme-pill__btn theme-pill__btn--light ${currentTheme === family.lightTheme ? "active" : ""}`,
|
|
64
|
+
onClick: () => onThemeChange(family.lightTheme),
|
|
65
|
+
"aria-label": family.lightLabel,
|
|
66
|
+
children: /* @__PURE__ */ jsx("div", { className: "theme-pill__icon", children: lightIcon })
|
|
67
|
+
}
|
|
68
|
+
) }),
|
|
69
|
+
/* @__PURE__ */ jsx(Tooltip, { content: family.darkLabel, children: /* @__PURE__ */ jsx(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
className: `theme-pill__btn theme-pill__btn--dark ${currentTheme === family.darkTheme ? "active" : ""}`,
|
|
73
|
+
onClick: () => onThemeChange(family.darkTheme),
|
|
74
|
+
"aria-label": family.darkLabel,
|
|
75
|
+
children: /* @__PURE__ */ jsx("div", { className: "theme-pill__icon", children: darkIcon })
|
|
76
|
+
}
|
|
77
|
+
) })
|
|
78
|
+
] }, idx);
|
|
79
|
+
}) }),
|
|
80
|
+
onSettingsClick && /* @__PURE__ */ jsx(Tooltip, { content: "Settings", children: /* @__PURE__ */ jsx(
|
|
81
|
+
"button",
|
|
82
|
+
{
|
|
83
|
+
className: "theme-picker__settings-icon",
|
|
84
|
+
onClick: () => {
|
|
85
|
+
onSettingsClick();
|
|
86
|
+
onToggle();
|
|
87
|
+
},
|
|
88
|
+
"aria-label": "Settings",
|
|
89
|
+
children: /* @__PURE__ */ jsx(SettingsIcon, {})
|
|
90
|
+
}
|
|
91
|
+
) })
|
|
92
|
+
] })
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
isOpen && /* @__PURE__ */ jsx("div", { className: "theme-picker__overlay", onClick: onToggle })
|
|
96
|
+
] });
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
ThemePicker
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=ThemePicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemePicker.js","sources":["../../src/components/ThemePicker.tsx"],"sourcesContent":["/**\n * ThemePicker component\n * A beautiful theme picker with light/dark variants for each theme family\n * Designed to work with any theme system via configuration\n */\n\nimport React, { useRef, useLayoutEffect, useState } from 'react'\nimport { Tooltip } from './Tooltip'\nimport type { ThemePickerProps } from '../types'\nimport { SettingsIcon, getFallbackIcon } from './ThemeIcons'\n// MoonIcon is a theme-family icon and lives with the theme model now.\nimport { MoonIcon } from '@wolffm/themes'\nimport '../theme-picker.css'\n\n/**\n * ThemePicker - Dropdown theme selector with optional settings button\n *\n * @example\n * ```tsx\n * import { ThemePicker } from '@wolffm/task-ui-components'\n * import '@wolffm/task-ui-components/theme-picker.css'\n *\n * <ThemePicker\n * currentTheme=\"dark\"\n * isOpen={showPicker}\n * themeFamilies={MY_THEME_FAMILIES}\n * onThemeChange={setTheme}\n * onToggle={() => setShowPicker(!showPicker)}\n * onSettingsClick={() => openSettings()}\n * />\n * ```\n */\nexport function ThemePicker({\n currentTheme,\n isOpen,\n themeFamilies,\n onThemeChange,\n onToggle,\n onSettingsClick,\n getThemeIcon,\n className = '',\n dropdownPlacement = 'auto'\n}: ThemePickerProps) {\n const dropdownRef = useRef<HTMLDivElement>(null)\n const [resolvedPlacement, setResolvedPlacement] = useState<'left' | 'right'>('right')\n\n // Edge detection: when 'auto', measure the dropdown after render and flip if it overflows\n useLayoutEffect(() => {\n if (!isOpen) return\n\n if (dropdownPlacement !== 'auto') {\n setResolvedPlacement(dropdownPlacement)\n return\n }\n\n // Start with right (default), then check if it overflows\n setResolvedPlacement('right')\n\n // Use rAF to measure after the browser has laid out the dropdown\n const frameId = requestAnimationFrame(() => {\n const el = dropdownRef.current\n if (!el) return\n const rect = el.getBoundingClientRect()\n if (rect.right > window.innerWidth) {\n setResolvedPlacement('left')\n }\n })\n\n return () => cancelAnimationFrame(frameId)\n }, [isOpen, dropdownPlacement])\n\n // Get icon for current theme (use provided function or fallback to MoonIcon)\n const currentIcon = getThemeIcon ? getThemeIcon(currentTheme) : <MoonIcon />\n\n const openLeftClass = resolvedPlacement === 'left' ? 'theme-picker--open-left' : ''\n\n return (\n <div className={`theme-picker ${openLeftClass} ${className}`.trim()}>\n <Tooltip content=\"Choose theme\">\n <button\n className=\"app-header__icon-btn theme-toggle-btn\"\n onClick={onToggle}\n aria-label=\"Choose theme\"\n >\n {currentIcon}\n </button>\n </Tooltip>\n {isOpen && (\n <div\n ref={dropdownRef}\n className=\"theme-picker__dropdown\"\n onClick={e => e.stopPropagation()}\n >\n <div className=\"theme-picker__main\">\n <div className=\"theme-picker__pills\">\n {themeFamilies.map((family, idx) => {\n // Use provided icons or fallback to generic shapes\n const lightIcon = family.lightIcon ?? getFallbackIcon(idx)\n const darkIcon = family.darkIcon ?? getFallbackIcon(idx)\n\n return (\n <div key={idx} className=\"theme-pill\">\n {/* Light variant button */}\n <Tooltip content={family.lightLabel}>\n <button\n className={`theme-pill__btn theme-pill__btn--light ${currentTheme === family.lightTheme ? 'active' : ''}`}\n onClick={() => onThemeChange(family.lightTheme)}\n aria-label={family.lightLabel}\n >\n <div className=\"theme-pill__icon\">{lightIcon}</div>\n </button>\n </Tooltip>\n\n {/* Dark variant button */}\n <Tooltip content={family.darkLabel}>\n <button\n className={`theme-pill__btn theme-pill__btn--dark ${currentTheme === family.darkTheme ? 'active' : ''}`}\n onClick={() => onThemeChange(family.darkTheme)}\n aria-label={family.darkLabel}\n >\n <div className=\"theme-pill__icon\">{darkIcon}</div>\n </button>\n </Tooltip>\n </div>\n )\n })}\n </div>\n {/* Settings button - separate column on the right */}\n {onSettingsClick && (\n <Tooltip content=\"Settings\">\n <button\n className=\"theme-picker__settings-icon\"\n onClick={() => {\n onSettingsClick()\n onToggle() // Close picker after opening settings\n }}\n aria-label=\"Settings\"\n >\n <SettingsIcon />\n </button>\n </Tooltip>\n )}\n </div>\n </div>\n )}\n {isOpen && <div className=\"theme-picker__overlay\" onClick={onToggle} />}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast Notification Component
|
|
3
|
+
* Displays temporary notification messages with different types
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
export interface ToastProps {
|
|
7
|
+
message: string;
|
|
8
|
+
type?: 'success' | 'error' | 'info' | 'warning';
|
|
9
|
+
duration?: number;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function Toast({ message, type, duration, onClose }: ToastProps): React.JSX.Element;
|
|
13
|
+
//# sourceMappingURL=Toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../src/components/Toast.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAmBD,wBAAgB,KAAK,CAAC,EAAE,OAAO,EAAE,IAAa,EAAE,QAAe,EAAE,OAAO,EAAE,EAAE,UAAU,qBA4DrF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
const renderIcon = (children) => /* @__PURE__ */ jsx(
|
|
4
|
+
"svg",
|
|
5
|
+
{
|
|
6
|
+
className: "toast__icon",
|
|
7
|
+
width: "20",
|
|
8
|
+
height: "20",
|
|
9
|
+
viewBox: "0 0 24 24",
|
|
10
|
+
fill: "none",
|
|
11
|
+
stroke: "currentColor",
|
|
12
|
+
strokeWidth: "2",
|
|
13
|
+
children
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
function Toast({ message, type = "info", duration = 3e3, onClose }) {
|
|
17
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const showTimer = setTimeout(() => {
|
|
20
|
+
setIsVisible(true);
|
|
21
|
+
}, 50);
|
|
22
|
+
const hideTimer = setTimeout(() => {
|
|
23
|
+
setIsVisible(false);
|
|
24
|
+
setTimeout(onClose, 500);
|
|
25
|
+
}, duration);
|
|
26
|
+
return () => {
|
|
27
|
+
clearTimeout(showTimer);
|
|
28
|
+
clearTimeout(hideTimer);
|
|
29
|
+
};
|
|
30
|
+
}, [duration, onClose]);
|
|
31
|
+
const getIcon = () => {
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "success":
|
|
34
|
+
return renderIcon(/* @__PURE__ */ jsx("path", { d: "M20 6L9 17l-5-5" }));
|
|
35
|
+
case "error":
|
|
36
|
+
return renderIcon(
|
|
37
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
38
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
39
|
+
/* @__PURE__ */ jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
|
|
40
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
|
|
41
|
+
] })
|
|
42
|
+
);
|
|
43
|
+
case "warning":
|
|
44
|
+
return renderIcon(
|
|
45
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
46
|
+
/* @__PURE__ */ jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
|
|
47
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
|
|
48
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
|
|
49
|
+
] })
|
|
50
|
+
);
|
|
51
|
+
case "info":
|
|
52
|
+
default:
|
|
53
|
+
return renderIcon(
|
|
54
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
56
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
57
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
58
|
+
] })
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return /* @__PURE__ */ jsxs("div", { className: `toast toast--${type} ${isVisible ? "toast--visible" : ""}`, children: [
|
|
63
|
+
getIcon(),
|
|
64
|
+
/* @__PURE__ */ jsx("span", { className: "toast__message", children: message })
|
|
65
|
+
] });
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
Toast
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=Toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toast.js","sources":["../../src/components/Toast.tsx"],"sourcesContent":["/**\r\n * Toast Notification Component\r\n * Displays temporary notification messages with different types\r\n */\r\n\r\nimport React, { useEffect, useState } from 'react'\r\n\r\nexport interface ToastProps {\r\n message: string\r\n type?: 'success' | 'error' | 'info' | 'warning'\r\n duration?: number\r\n onClose: () => void\r\n}\r\n\r\n/**\r\n * Helper function to render SVG icon wrapper with consistent styling\r\n */\r\nconst renderIcon = (children: React.ReactNode) => (\r\n <svg\r\n className=\"toast__icon\"\r\n width=\"20\"\r\n height=\"20\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n >\r\n {children}\r\n </svg>\r\n)\r\n\r\nexport function Toast({ message, type = 'info', duration = 3000, onClose }: ToastProps) {\r\n const [isVisible, setIsVisible] = useState(false)\r\n\r\n useEffect(() => {\r\n // Fade in with slight delay for smoother appearance\r\n const showTimer = setTimeout(() => {\r\n setIsVisible(true)\r\n }, 50)\r\n\r\n // Auto-dismiss after duration\r\n const hideTimer = setTimeout(() => {\r\n setIsVisible(false)\r\n // Wait for fade-out animation before calling onClose\r\n setTimeout(onClose, 500)\r\n }, duration)\r\n\r\n return () => {\r\n clearTimeout(showTimer)\r\n clearTimeout(hideTimer)\r\n }\r\n }, [duration, onClose])\r\n\r\n const getIcon = () => {\r\n switch (type) {\r\n case 'success':\r\n return renderIcon(<path d=\"M20 6L9 17l-5-5\" />)\r\n case 'error':\r\n return renderIcon(\r\n <>\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\" />\r\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\" />\r\n </>\r\n )\r\n case 'warning':\r\n return renderIcon(\r\n <>\r\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\" />\r\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\" />\r\n <line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\" />\r\n </>\r\n )\r\n case 'info':\r\n default:\r\n return renderIcon(\r\n <>\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\" />\r\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\" />\r\n </>\r\n )\r\n }\r\n }\r\n\r\n return (\r\n <div className={`toast toast--${type} ${isVisible ? 'toast--visible' : ''}`}>\r\n {getIcon()}\r\n <span className=\"toast__message\">{message}</span>\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;AAiBA,MAAM,aAAa,CAAC,aAClB;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,WAAU;AAAA,IACV,OAAM;AAAA,IACN,QAAO;AAAA,IACP,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,QAAO;AAAA,IACP,aAAY;AAAA,IAEX;AAAA,EAAA;AACH;AAGK,SAAS,MAAM,EAAE,SAAS,OAAO,QAAQ,WAAW,KAAM,WAAuB;AACtF,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,YAAU,MAAM;AAEd,UAAM,YAAY,WAAW,MAAM;AACjC,mBAAa,IAAI;AAAA,IACnB,GAAG,EAAE;AAGL,UAAM,YAAY,WAAW,MAAM;AACjC,mBAAa,KAAK;AAElB,iBAAW,SAAS,GAAG;AAAA,IACzB,GAAG,QAAQ;AAEX,WAAO,MAAM;AACX,mBAAa,SAAS;AACtB,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,UAAU,OAAO,CAAC;AAEtB,QAAM,UAAU,MAAM;AACpB,YAAQ,MAAA;AAAA,MACN,KAAK;AACH,eAAO,WAAW,oBAAC,QAAA,EAAK,GAAE,mBAAkB,CAAE;AAAA,MAChD,KAAK;AACH,eAAO;AAAA,UACL,qBAAA,UAAA,EACE,UAAA;AAAA,YAAA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,YAC/B,oBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI,IAAG,KAAA,CAAK;AAAA,YACpC,oBAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,KAAA,CAAK;AAAA,UAAA,EAAA,CACtC;AAAA,QAAA;AAAA,MAEJ,KAAK;AACH,eAAO;AAAA,UACL,qBAAA,UAAA,EACE,UAAA;AAAA,YAAA,oBAAC,QAAA,EAAK,GAAE,2FAAA,CAA2F;AAAA,YACnG,oBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,KAAA,CAAK;AAAA,YACrC,oBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,SAAQ,IAAG,KAAA,CAAK;AAAA,UAAA,EAAA,CAC3C;AAAA,QAAA;AAAA,MAEJ,KAAK;AAAA,MACL;AACE,eAAO;AAAA,UACL,qBAAA,UAAA,EACE,UAAA;AAAA,YAAA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,YAC/B,oBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAA,CAAK;AAAA,YACtC,oBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,SAAQ,IAAG,IAAA,CAAI;AAAA,UAAA,EAAA,CACzC;AAAA,QAAA;AAAA,IACF;AAAA,EAEN;AAEA,SACE,qBAAC,SAAI,WAAW,gBAAgB,IAAI,IAAI,YAAY,mBAAmB,EAAE,IACtE,UAAA;AAAA,IAAA,QAAA;AAAA,IACD,oBAAC,QAAA,EAAK,WAAU,kBAAkB,UAAA,QAAA,CAAQ;AAAA,EAAA,GAC5C;AAEJ;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toaster Container Component
|
|
3
|
+
* Manages and displays multiple toast notifications
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import '../toaster.css';
|
|
7
|
+
export interface ToastState {
|
|
8
|
+
id: number;
|
|
9
|
+
message: string;
|
|
10
|
+
type: 'success' | 'error' | 'info' | 'warning';
|
|
11
|
+
duration?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ToasterProps {
|
|
14
|
+
toasts: ToastState[];
|
|
15
|
+
onDismiss: (id: number) => void;
|
|
16
|
+
position?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
17
|
+
}
|
|
18
|
+
export declare function Toaster({ toasts, onDismiss, position }: ToasterProps): React.JSX.Element;
|
|
19
|
+
//# sourceMappingURL=Toaster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../src/components/Toaster.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,gBAAgB,CAAA;AAEvB,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,QAAQ,CAAC,EACL,UAAU,GACV,YAAY,GACZ,WAAW,GACX,aAAa,GACb,eAAe,GACf,cAAc,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAA0B,EAAE,EAAE,YAAY,qBActF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Toast } from "./Toast.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
function Toaster({ toasts, onDismiss, position = "bottom-center" }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { className: `toast-container toast-container--${position}`, children: toasts.map((toast) => /* @__PURE__ */ jsx(
|
|
6
|
+
Toast,
|
|
7
|
+
{
|
|
8
|
+
message: toast.message,
|
|
9
|
+
type: toast.type,
|
|
10
|
+
duration: toast.duration,
|
|
11
|
+
onClose: () => onDismiss(toast.id)
|
|
12
|
+
},
|
|
13
|
+
toast.id
|
|
14
|
+
)) });
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
Toaster
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=Toaster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toaster.js","sources":["../../src/components/Toaster.tsx"],"sourcesContent":["/**\r\n * Toaster Container Component\r\n * Manages and displays multiple toast notifications\r\n */\r\n\r\nimport React from 'react'\r\nimport { Toast } from './Toast'\r\nimport '../toaster.css'\r\n\r\nexport interface ToastState {\r\n id: number\r\n message: string\r\n type: 'success' | 'error' | 'info' | 'warning'\r\n duration?: number\r\n}\r\n\r\nexport interface ToasterProps {\r\n toasts: ToastState[]\r\n onDismiss: (id: number) => void\r\n position?:\r\n | 'top-left'\r\n | 'top-center'\r\n | 'top-right'\r\n | 'bottom-left'\r\n | 'bottom-center'\r\n | 'bottom-right'\r\n}\r\n\r\nexport function Toaster({ toasts, onDismiss, position = 'bottom-center' }: ToasterProps) {\r\n return (\r\n <div className={`toast-container toast-container--${position}`}>\r\n {toasts.map(toast => (\r\n <Toast\r\n key={toast.id}\r\n message={toast.message}\r\n type={toast.type}\r\n duration={toast.duration}\r\n onClose={() => onDismiss(toast.id)}\r\n />\r\n ))}\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;AA4BO,SAAS,QAAQ,EAAE,QAAQ,WAAW,WAAW,mBAAiC;AACvF,SACE,oBAAC,SAAI,WAAW,oCAAoC,QAAQ,IACzD,UAAA,OAAO,IAAI,CAAA,UACV;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,SAAS,MAAM;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM,UAAU,MAAM,EAAE;AAAA,IAAA;AAAA,IAJ5B,MAAM;AAAA,EAAA,CAMd,GACH;AAEJ;"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tooltip Component
|
|
3
|
+
* Themed replacement for `title=`. A native title tooltip is OS-drawn, waits
|
|
4
|
+
* about a second, never appears on keyboard focus and cannot be themed.
|
|
5
|
+
*
|
|
6
|
+
* Meets WCAG 1.4.13 (content on hover or focus): it shows on hover AND focus,
|
|
7
|
+
* Escape dismisses it, and the pointer can move onto the bubble without it
|
|
8
|
+
* closing. The child gets `aria-describedby`, so the text is announced —
|
|
9
|
+
* unless it is the child's own `aria-label`, which would read it twice. That
|
|
10
|
+
* is the common case for an icon button: the label names it, and the tooltip
|
|
11
|
+
* shows sighted users the same words.
|
|
12
|
+
*
|
|
13
|
+
* Like <Select>, the bubble is portalled into the anchor's theme scope (see
|
|
14
|
+
* themeScopeOf), so it keeps the theme's tokens and is placed correctly even
|
|
15
|
+
* under a transformed ancestor.
|
|
16
|
+
*/
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import '../tooltip.css';
|
|
19
|
+
import { type TooltipPlacement } from '../lib/tooltipPlacement';
|
|
20
|
+
export type { TooltipPlacement };
|
|
21
|
+
export type TooltipVariant = 'inverse' | 'card' | 'primary';
|
|
22
|
+
export interface TooltipProps {
|
|
23
|
+
content: React.ReactNode;
|
|
24
|
+
/** Preferred side; flips to the opposite one when it would leave the viewport. */
|
|
25
|
+
placement?: TooltipPlacement;
|
|
26
|
+
variant?: TooltipVariant;
|
|
27
|
+
/** Hover delay before showing, ms. Focus shows immediately. */
|
|
28
|
+
delay?: number;
|
|
29
|
+
/** Render the child alone, e.g. while a menu it belongs to is open. */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** A single element. Tooltip attaches its ref and pointer/focus handlers to it
|
|
32
|
+
* directly (composed with the child's own) and adds aria-describedby. */
|
|
33
|
+
children: React.ReactElement<AnchorProps>;
|
|
34
|
+
}
|
|
35
|
+
/** What Tooltip reads from, and adds to, its child. No wrapper element: a
|
|
36
|
+
* wrapper — even `display: contents` — still changes the DOM tree, and with it
|
|
37
|
+
* every `:last-child`, `>` and `+` selector aimed at the child. */
|
|
38
|
+
interface AnchorProps {
|
|
39
|
+
'aria-describedby'?: string;
|
|
40
|
+
'aria-label'?: string;
|
|
41
|
+
ref?: React.Ref<HTMLElement>;
|
|
42
|
+
onPointerEnter?: React.PointerEventHandler<HTMLElement>;
|
|
43
|
+
onPointerLeave?: React.PointerEventHandler<HTMLElement>;
|
|
44
|
+
onPointerDown?: React.PointerEventHandler<HTMLElement>;
|
|
45
|
+
onFocus?: React.FocusEventHandler<HTMLElement>;
|
|
46
|
+
onBlur?: React.FocusEventHandler<HTMLElement>;
|
|
47
|
+
}
|
|
48
|
+
export declare function Tooltip({ content, placement, variant, delay, disabled, children }: TooltipProps): React.JSX.Element;
|
|
49
|
+
//# sourceMappingURL=Tooltip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../src/components/Tooltip.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAA2E,MAAM,OAAO,CAAA;AAE/F,OAAO,gBAAgB,CAAA;AAEvB,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1F,YAAY,EAAE,gBAAgB,EAAE,CAAA;AAChC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAE3D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,kFAAkF;IAClF,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;8EAC0E;IAC1E,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;CAC1C;AAED;;oEAEoE;AACpE,UAAU,WAAW;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAA;IACvD,cAAc,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAA;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAA;IACtD,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;CAC9C;AAWD,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,SAAiB,EACjB,OAAgB,EAChB,KAAW,EACX,QAAgB,EAChB,QAAQ,EACT,EAAE,YAAY,qBA6Id"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useId, useState, useRef, useCallback, useEffect, useLayoutEffect } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
/* empty css */
|
|
5
|
+
import { themeScopeOf } from "../lib/themeScope.js";
|
|
6
|
+
import { placeTooltip } from "../lib/tooltipPlacement.js";
|
|
7
|
+
function childRefOf(child) {
|
|
8
|
+
if (!React.version.startsWith("18")) return child.props.ref;
|
|
9
|
+
return child.ref ?? void 0;
|
|
10
|
+
}
|
|
11
|
+
const HIDE_GRACE_MS = 120;
|
|
12
|
+
function Tooltip({
|
|
13
|
+
content,
|
|
14
|
+
placement = "top",
|
|
15
|
+
variant = "card",
|
|
16
|
+
delay = 300,
|
|
17
|
+
disabled = false,
|
|
18
|
+
children
|
|
19
|
+
}) {
|
|
20
|
+
const tipId = useId();
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
const [placed, setPlaced] = useState(null);
|
|
23
|
+
const [scope, setScope] = useState(null);
|
|
24
|
+
const anchorRef = useRef(null);
|
|
25
|
+
const bubbleRef = useRef(null);
|
|
26
|
+
const timer = useRef(void 0);
|
|
27
|
+
const show = useCallback((after) => {
|
|
28
|
+
clearTimeout(timer.current);
|
|
29
|
+
timer.current = setTimeout(() => {
|
|
30
|
+
setScope(themeScopeOf(anchorRef.current));
|
|
31
|
+
setOpen(true);
|
|
32
|
+
}, after);
|
|
33
|
+
}, []);
|
|
34
|
+
const hide = useCallback((after) => {
|
|
35
|
+
clearTimeout(timer.current);
|
|
36
|
+
timer.current = setTimeout(() => {
|
|
37
|
+
setOpen(false);
|
|
38
|
+
setPlaced(null);
|
|
39
|
+
}, after);
|
|
40
|
+
}, []);
|
|
41
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
42
|
+
const childRef = childRefOf(children);
|
|
43
|
+
const setAnchor = useCallback(
|
|
44
|
+
(node) => {
|
|
45
|
+
anchorRef.current = node;
|
|
46
|
+
if (typeof childRef === "function") childRef(node);
|
|
47
|
+
else if (childRef) childRef.current = node;
|
|
48
|
+
},
|
|
49
|
+
[childRef]
|
|
50
|
+
);
|
|
51
|
+
const place = useCallback(() => {
|
|
52
|
+
const target = anchorRef.current;
|
|
53
|
+
const bubble = bubbleRef.current;
|
|
54
|
+
if (!target || !bubble) return;
|
|
55
|
+
setPlaced(
|
|
56
|
+
placeTooltip(
|
|
57
|
+
placement,
|
|
58
|
+
target.getBoundingClientRect(),
|
|
59
|
+
bubble.offsetWidth,
|
|
60
|
+
bubble.offsetHeight
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}, [placement]);
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
if (open && scope) place();
|
|
66
|
+
}, [open, scope, place, content]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!open) return;
|
|
69
|
+
const onKey = (e) => {
|
|
70
|
+
if (e.key === "Escape") hide(0);
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener("scroll", place, true);
|
|
73
|
+
window.addEventListener("resize", place);
|
|
74
|
+
document.addEventListener("keydown", onKey);
|
|
75
|
+
return () => {
|
|
76
|
+
window.removeEventListener("scroll", place, true);
|
|
77
|
+
window.removeEventListener("resize", place);
|
|
78
|
+
document.removeEventListener("keydown", onKey);
|
|
79
|
+
};
|
|
80
|
+
}, [open, place, hide]);
|
|
81
|
+
if (disabled || content == null || content === "") return children;
|
|
82
|
+
const visible = open && placed !== null;
|
|
83
|
+
const restatesLabel = typeof content === "string" && content === children.props["aria-label"];
|
|
84
|
+
const style = placed ? {
|
|
85
|
+
left: placed.left,
|
|
86
|
+
top: placed.top,
|
|
87
|
+
"--hdk-tooltip-arrow": `${placed.arrow}px`
|
|
88
|
+
} : { left: 0, top: 0 };
|
|
89
|
+
const own = children.props;
|
|
90
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
|
+
React.cloneElement(children, {
|
|
92
|
+
ref: setAnchor,
|
|
93
|
+
onPointerEnter: (e) => {
|
|
94
|
+
own.onPointerEnter?.(e);
|
|
95
|
+
if (e.pointerType === "mouse") show(delay);
|
|
96
|
+
},
|
|
97
|
+
onPointerLeave: (e) => {
|
|
98
|
+
own.onPointerLeave?.(e);
|
|
99
|
+
if (e.pointerType === "mouse") hide(HIDE_GRACE_MS);
|
|
100
|
+
},
|
|
101
|
+
// A press dismisses it, as it does a native title: the click usually
|
|
102
|
+
// opens something (a menu, a dialog) the bubble would sit on top of.
|
|
103
|
+
onPointerDown: (e) => {
|
|
104
|
+
own.onPointerDown?.(e);
|
|
105
|
+
hide(0);
|
|
106
|
+
},
|
|
107
|
+
onFocus: (e) => {
|
|
108
|
+
own.onFocus?.(e);
|
|
109
|
+
if (e.currentTarget.matches(":focus-visible")) show(0);
|
|
110
|
+
},
|
|
111
|
+
onBlur: (e) => {
|
|
112
|
+
own.onBlur?.(e);
|
|
113
|
+
hide(0);
|
|
114
|
+
},
|
|
115
|
+
"aria-describedby": [own["aria-describedby"], open && !restatesLabel ? tipId : null].filter(Boolean).join(" ") || void 0
|
|
116
|
+
}),
|
|
117
|
+
open && scope && createPortal(
|
|
118
|
+
/* @__PURE__ */ jsx(
|
|
119
|
+
"div",
|
|
120
|
+
{
|
|
121
|
+
ref: bubbleRef,
|
|
122
|
+
id: tipId,
|
|
123
|
+
role: "tooltip",
|
|
124
|
+
className: [
|
|
125
|
+
"hdk-tooltip",
|
|
126
|
+
`hdk-tooltip--${variant}`,
|
|
127
|
+
`hdk-tooltip--${placed?.side ?? placement}`,
|
|
128
|
+
visible && "hdk-tooltip--visible"
|
|
129
|
+
].filter(Boolean).join(" "),
|
|
130
|
+
style,
|
|
131
|
+
onPointerEnter: () => clearTimeout(timer.current),
|
|
132
|
+
onPointerLeave: () => hide(HIDE_GRACE_MS),
|
|
133
|
+
children: content
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
scope
|
|
137
|
+
)
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
Tooltip
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=Tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tooltip.js","sources":["../../src/components/Tooltip.tsx"],"sourcesContent":["/**\n * Tooltip Component\n * Themed replacement for `title=`. A native title tooltip is OS-drawn, waits\n * about a second, never appears on keyboard focus and cannot be themed.\n *\n * Meets WCAG 1.4.13 (content on hover or focus): it shows on hover AND focus,\n * Escape dismisses it, and the pointer can move onto the bubble without it\n * closing. The child gets `aria-describedby`, so the text is announced —\n * unless it is the child's own `aria-label`, which would read it twice. That\n * is the common case for an icon button: the label names it, and the tooltip\n * shows sighted users the same words.\n *\n * Like <Select>, the bubble is portalled into the anchor's theme scope (see\n * themeScopeOf), so it keeps the theme's tokens and is placed correctly even\n * under a transformed ancestor.\n */\n\nimport React, { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport '../tooltip.css'\nimport { themeScopeOf } from '../lib/themeScope'\nimport { placeTooltip, type Placed, type TooltipPlacement } from '../lib/tooltipPlacement'\n\nexport type { TooltipPlacement }\nexport type TooltipVariant = 'inverse' | 'card' | 'primary'\n\nexport interface TooltipProps {\n content: React.ReactNode\n /** Preferred side; flips to the opposite one when it would leave the viewport. */\n placement?: TooltipPlacement\n variant?: TooltipVariant\n /** Hover delay before showing, ms. Focus shows immediately. */\n delay?: number\n /** Render the child alone, e.g. while a menu it belongs to is open. */\n disabled?: boolean\n /** A single element. Tooltip attaches its ref and pointer/focus handlers to it\n * directly (composed with the child's own) and adds aria-describedby. */\n children: React.ReactElement<AnchorProps>\n}\n\n/** What Tooltip reads from, and adds to, its child. No wrapper element: a\n * wrapper — even `display: contents` — still changes the DOM tree, and with it\n * every `:last-child`, `>` and `+` selector aimed at the child. */\ninterface AnchorProps {\n 'aria-describedby'?: string\n 'aria-label'?: string\n ref?: React.Ref<HTMLElement>\n onPointerEnter?: React.PointerEventHandler<HTMLElement>\n onPointerLeave?: React.PointerEventHandler<HTMLElement>\n onPointerDown?: React.PointerEventHandler<HTMLElement>\n onFocus?: React.FocusEventHandler<HTMLElement>\n onBlur?: React.FocusEventHandler<HTMLElement>\n}\n\n/** The child's own ref: a prop in React 19, on the element in React 18 (where\n * reading it from props is always undefined, and in 19 `element.ref` warns). */\nfunction childRefOf(child: React.ReactElement<AnchorProps>): React.Ref<HTMLElement> | undefined {\n if (!React.version.startsWith('18')) return child.props.ref\n return (child as unknown as { ref?: React.Ref<HTMLElement> }).ref ?? undefined\n}\n\nconst HIDE_GRACE_MS = 120\n\nexport function Tooltip({\n content,\n placement = 'top',\n variant = 'card',\n delay = 300,\n disabled = false,\n children\n}: TooltipProps) {\n const tipId = useId()\n const [open, setOpen] = useState(false)\n const [placed, setPlaced] = useState<Placed | null>(null)\n const [scope, setScope] = useState<HTMLElement | null>(null)\n const anchorRef = useRef<HTMLElement | null>(null)\n const bubbleRef = useRef<HTMLDivElement>(null)\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n const show = useCallback((after: number) => {\n clearTimeout(timer.current)\n timer.current = setTimeout(() => {\n setScope(themeScopeOf(anchorRef.current))\n setOpen(true)\n }, after)\n }, [])\n\n const hide = useCallback((after: number) => {\n clearTimeout(timer.current)\n timer.current = setTimeout(() => {\n setOpen(false)\n setPlaced(null)\n }, after)\n }, [])\n\n useEffect(() => () => clearTimeout(timer.current), [])\n\n const childRef = childRefOf(children)\n const setAnchor = useCallback(\n (node: HTMLElement | null) => {\n anchorRef.current = node\n if (typeof childRef === 'function') childRef(node)\n else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = node\n },\n [childRef]\n )\n\n const place = useCallback(() => {\n const target = anchorRef.current\n const bubble = bubbleRef.current\n if (!target || !bubble) return\n setPlaced(\n placeTooltip(\n placement,\n target.getBoundingClientRect(),\n bubble.offsetWidth,\n bubble.offsetHeight\n )\n )\n }, [placement])\n\n useLayoutEffect(() => {\n if (open && scope) place()\n }, [open, scope, place, content])\n\n useEffect(() => {\n if (!open) return\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') hide(0)\n }\n window.addEventListener('scroll', place, true)\n window.addEventListener('resize', place)\n document.addEventListener('keydown', onKey)\n return () => {\n window.removeEventListener('scroll', place, true)\n window.removeEventListener('resize', place)\n document.removeEventListener('keydown', onKey)\n }\n }, [open, place, hide])\n\n if (disabled || content == null || content === '') return children\n\n const visible = open && placed !== null\n const restatesLabel = typeof content === 'string' && content === children.props['aria-label']\n const style: React.CSSProperties = placed\n ? ({\n left: placed.left,\n top: placed.top,\n '--hdk-tooltip-arrow': `${placed.arrow}px`\n } as React.CSSProperties)\n : { left: 0, top: 0 }\n\n const own = children.props\n return (\n <>\n {React.cloneElement(children, {\n ref: setAnchor,\n onPointerEnter: e => {\n own.onPointerEnter?.(e)\n if (e.pointerType === 'mouse') show(delay)\n },\n onPointerLeave: e => {\n own.onPointerLeave?.(e)\n if (e.pointerType === 'mouse') hide(HIDE_GRACE_MS)\n },\n // A press dismisses it, as it does a native title: the click usually\n // opens something (a menu, a dialog) the bubble would sit on top of.\n onPointerDown: e => {\n own.onPointerDown?.(e)\n hide(0)\n },\n onFocus: e => {\n own.onFocus?.(e)\n // Keyboard focus only — a mouse click focuses too, and the hover\n // path already handles that pointer.\n if (e.currentTarget.matches(':focus-visible')) show(0)\n },\n onBlur: e => {\n own.onBlur?.(e)\n hide(0)\n },\n 'aria-describedby':\n [own['aria-describedby'], open && !restatesLabel ? tipId : null]\n .filter(Boolean)\n .join(' ') || undefined\n })}\n {open &&\n scope &&\n createPortal(\n <div\n ref={bubbleRef}\n id={tipId}\n role=\"tooltip\"\n className={[\n 'hdk-tooltip',\n `hdk-tooltip--${variant}`,\n `hdk-tooltip--${placed?.side ?? placement}`,\n visible && 'hdk-tooltip--visible'\n ]\n .filter(Boolean)\n .join(' ')}\n style={style}\n onPointerEnter={() => clearTimeout(timer.current)}\n onPointerLeave={() => hide(HIDE_GRACE_MS)}\n >\n {content}\n </div>,\n scope\n )}\n </>\n )\n}\n"],"names":[],"mappings":"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"}
|