@harbinger-ai/harbinger 0.1.0
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 +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
const SheetContext = createContext({ open: false, onOpenChange: () => {} });
|
|
7
|
+
|
|
8
|
+
export function Sheet({ children, open, onOpenChange }) {
|
|
9
|
+
return (
|
|
10
|
+
<SheetContext.Provider value={{ open, onOpenChange }}>
|
|
11
|
+
{children}
|
|
12
|
+
</SheetContext.Provider>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function SheetTrigger({ children, asChild, ...props }) {
|
|
17
|
+
const { onOpenChange } = useContext(SheetContext);
|
|
18
|
+
if (asChild && children) {
|
|
19
|
+
return (
|
|
20
|
+
<span onClick={() => onOpenChange(true)} {...props}>
|
|
21
|
+
{children}
|
|
22
|
+
</span>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return (
|
|
26
|
+
<button onClick={() => onOpenChange(true)} {...props}>
|
|
27
|
+
{children}
|
|
28
|
+
</button>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function SheetContent({ children, className, side = 'left', ...props }) {
|
|
33
|
+
const { open, onOpenChange } = useContext(SheetContext);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!open) return;
|
|
37
|
+
const handleEsc = (e) => {
|
|
38
|
+
if (e.key === 'Escape') onOpenChange(false);
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener('keydown', handleEsc);
|
|
41
|
+
return () => document.removeEventListener('keydown', handleEsc);
|
|
42
|
+
}, [open, onOpenChange]);
|
|
43
|
+
|
|
44
|
+
if (!open) return null;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="fixed inset-0 z-50">
|
|
48
|
+
{/* Overlay */}
|
|
49
|
+
<div
|
|
50
|
+
className="fixed inset-0 bg-black/50"
|
|
51
|
+
onClick={() => onOpenChange(false)}
|
|
52
|
+
/>
|
|
53
|
+
{/* Content */}
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
'fixed z-50 bg-background shadow-lg transition-transform',
|
|
57
|
+
side === 'left' && 'inset-y-0 left-0 w-3/4 max-w-sm border-r border-border',
|
|
58
|
+
side === 'right' && 'inset-y-0 right-0 w-3/4 max-w-sm border-l border-border',
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function SheetHeader({ children, className }) {
|
|
70
|
+
return <div className={cn('flex flex-col space-y-2 p-4', className)}>{children}</div>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function SheetTitle({ children, className }) {
|
|
74
|
+
return <h2 className={cn('text-lg font-semibold text-foreground', className)}>{children}</h2>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function SheetDescription({ children, className }) {
|
|
78
|
+
return <p className={cn('text-sm text-muted-foreground', className)}>{children}</p>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function SheetClose({ children, asChild, ...props }) {
|
|
82
|
+
const { onOpenChange } = useContext(SheetContext);
|
|
83
|
+
if (asChild && children) {
|
|
84
|
+
return (
|
|
85
|
+
<span onClick={() => onOpenChange(false)} {...props}>
|
|
86
|
+
{children}
|
|
87
|
+
</span>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<button onClick={() => onOpenChange(false)} {...props}>
|
|
92
|
+
{children}
|
|
93
|
+
</button>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
import { Sheet, SheetContent } from "./sheet.js";
|
|
6
|
+
const SIDEBAR_WIDTH = "16rem";
|
|
7
|
+
const SIDEBAR_WIDTH_ICON = "3rem";
|
|
8
|
+
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
|
9
|
+
const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
|
10
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
11
|
+
const SidebarContext = createContext(null);
|
|
12
|
+
function useSidebar() {
|
|
13
|
+
const context = useContext(SidebarContext);
|
|
14
|
+
if (!context) {
|
|
15
|
+
throw new Error("useSidebar must be used within a SidebarProvider");
|
|
16
|
+
}
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
function SidebarProvider({
|
|
20
|
+
children,
|
|
21
|
+
defaultOpen = true,
|
|
22
|
+
open: openProp,
|
|
23
|
+
onOpenChange: setOpenProp
|
|
24
|
+
}) {
|
|
25
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
26
|
+
const [openMobile, setOpenMobile] = useState(false);
|
|
27
|
+
const [_open, _setOpen] = useState(defaultOpen);
|
|
28
|
+
const open = openProp !== void 0 ? openProp : _open;
|
|
29
|
+
const setOpen = useCallback(
|
|
30
|
+
(value) => {
|
|
31
|
+
const newOpen = typeof value === "function" ? value(open) : value;
|
|
32
|
+
if (setOpenProp) {
|
|
33
|
+
setOpenProp(newOpen);
|
|
34
|
+
} else {
|
|
35
|
+
_setOpen(newOpen);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${newOpen}; path=/; max-age=${60 * 60 * 24 * 7}`;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
[setOpenProp, open]
|
|
43
|
+
);
|
|
44
|
+
const toggleSidebar = useCallback(() => {
|
|
45
|
+
if (isMobile) {
|
|
46
|
+
setOpenMobile((prev) => !prev);
|
|
47
|
+
} else {
|
|
48
|
+
setOpen((prev) => !prev);
|
|
49
|
+
}
|
|
50
|
+
}, [isMobile, setOpen]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const check = () => setIsMobile(window.innerWidth < 768);
|
|
53
|
+
check();
|
|
54
|
+
window.addEventListener("resize", check);
|
|
55
|
+
return () => window.removeEventListener("resize", check);
|
|
56
|
+
}, []);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const handleKeyDown = (e) => {
|
|
59
|
+
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
toggleSidebar();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
65
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
66
|
+
}, [toggleSidebar]);
|
|
67
|
+
const state = open ? "expanded" : "collapsed";
|
|
68
|
+
const contextValue = useMemo(
|
|
69
|
+
() => ({
|
|
70
|
+
state,
|
|
71
|
+
open,
|
|
72
|
+
setOpen,
|
|
73
|
+
isMobile,
|
|
74
|
+
openMobile,
|
|
75
|
+
setOpenMobile,
|
|
76
|
+
toggleSidebar
|
|
77
|
+
}),
|
|
78
|
+
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
|
79
|
+
);
|
|
80
|
+
return /* @__PURE__ */ jsx(SidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
className: "group/sidebar-wrapper flex min-h-svh w-full",
|
|
84
|
+
style: {
|
|
85
|
+
"--sidebar-width": SIDEBAR_WIDTH,
|
|
86
|
+
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
87
|
+
"--sidebar-width-mobile": SIDEBAR_WIDTH_MOBILE
|
|
88
|
+
},
|
|
89
|
+
"data-sidebar-state": state,
|
|
90
|
+
children
|
|
91
|
+
}
|
|
92
|
+
) });
|
|
93
|
+
}
|
|
94
|
+
function Sidebar({ children, className, side = "left" }) {
|
|
95
|
+
const { isMobile, open, openMobile, setOpenMobile } = useSidebar();
|
|
96
|
+
if (isMobile) {
|
|
97
|
+
return /* @__PURE__ */ jsx(Sheet, { open: openMobile, onOpenChange: setOpenMobile, children: /* @__PURE__ */ jsx(
|
|
98
|
+
SheetContent,
|
|
99
|
+
{
|
|
100
|
+
side,
|
|
101
|
+
className: cn("w-[var(--sidebar-width-mobile)] p-0 [&>button]:hidden", className),
|
|
102
|
+
children: /* @__PURE__ */ jsx("div", { className: "flex h-full w-full flex-col", children })
|
|
103
|
+
}
|
|
104
|
+
) });
|
|
105
|
+
}
|
|
106
|
+
return /* @__PURE__ */ jsx(
|
|
107
|
+
"div",
|
|
108
|
+
{
|
|
109
|
+
className: cn(
|
|
110
|
+
"sticky top-0 flex h-svh flex-col border-r border-border bg-muted transition-[width] duration-200",
|
|
111
|
+
open ? "w-[var(--sidebar-width)]" : "w-[var(--sidebar-width-icon)]",
|
|
112
|
+
className
|
|
113
|
+
),
|
|
114
|
+
children: /* @__PURE__ */ jsx(
|
|
115
|
+
"div",
|
|
116
|
+
{
|
|
117
|
+
className: cn(
|
|
118
|
+
"flex h-full flex-col overflow-hidden",
|
|
119
|
+
open ? "w-[var(--sidebar-width)]" : "w-[var(--sidebar-width-icon)]"
|
|
120
|
+
),
|
|
121
|
+
children
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function SidebarHeader({ children, className }) {
|
|
128
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2 p-2", className), children });
|
|
129
|
+
}
|
|
130
|
+
function SidebarContent({ children, className }) {
|
|
131
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex min-h-0 flex-1 flex-col overflow-y-auto", className), children });
|
|
132
|
+
}
|
|
133
|
+
function SidebarFooter({ children, className }) {
|
|
134
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2 p-2", className), children });
|
|
135
|
+
}
|
|
136
|
+
function SidebarMenu({ children, className }) {
|
|
137
|
+
return /* @__PURE__ */ jsx("ul", { className: cn("flex w-full min-w-0 flex-col gap-1", className), children });
|
|
138
|
+
}
|
|
139
|
+
function SidebarMenuItem({ children, className }) {
|
|
140
|
+
return /* @__PURE__ */ jsx("li", { className: cn("group/menu-item relative", className), children });
|
|
141
|
+
}
|
|
142
|
+
function SidebarMenuButton({ children, className, isActive, asChild, tooltip, ...props }) {
|
|
143
|
+
const Tag = asChild ? "span" : "button";
|
|
144
|
+
return /* @__PURE__ */ jsx(
|
|
145
|
+
Tag,
|
|
146
|
+
{
|
|
147
|
+
className: cn(
|
|
148
|
+
"flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none transition-colors",
|
|
149
|
+
"hover:bg-background hover:text-foreground",
|
|
150
|
+
isActive && "bg-background text-foreground font-medium",
|
|
151
|
+
className
|
|
152
|
+
),
|
|
153
|
+
...props,
|
|
154
|
+
children
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function SidebarGroup({ children, className }) {
|
|
159
|
+
return /* @__PURE__ */ jsx("div", { className: cn("relative flex w-full min-w-0 flex-col p-2", className), children });
|
|
160
|
+
}
|
|
161
|
+
function SidebarGroupLabel({ children, className }) {
|
|
162
|
+
return /* @__PURE__ */ jsx(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
className: cn(
|
|
166
|
+
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground",
|
|
167
|
+
className
|
|
168
|
+
),
|
|
169
|
+
children
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function SidebarGroupContent({ children, className }) {
|
|
174
|
+
return /* @__PURE__ */ jsx("div", { className: cn("w-full", className), children });
|
|
175
|
+
}
|
|
176
|
+
function SidebarInset({ children, className }) {
|
|
177
|
+
return /* @__PURE__ */ jsx("main", { className: cn("relative flex min-h-svh flex-1 flex-col bg-background", className), children });
|
|
178
|
+
}
|
|
179
|
+
function SidebarTrigger({ className, ...props }) {
|
|
180
|
+
const { toggleSidebar } = useSidebar();
|
|
181
|
+
return /* @__PURE__ */ jsxs(
|
|
182
|
+
"button",
|
|
183
|
+
{
|
|
184
|
+
className: cn(
|
|
185
|
+
"inline-flex items-center justify-center rounded-md p-2 text-foreground hover:bg-muted",
|
|
186
|
+
className
|
|
187
|
+
),
|
|
188
|
+
onClick: toggleSidebar,
|
|
189
|
+
...props,
|
|
190
|
+
children: [
|
|
191
|
+
/* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "size-4", children: [
|
|
192
|
+
/* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
193
|
+
/* @__PURE__ */ jsx("path", { d: "M9 3v18" })
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Toggle Sidebar" })
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
function SidebarRail() {
|
|
201
|
+
const { toggleSidebar } = useSidebar();
|
|
202
|
+
return /* @__PURE__ */ jsx(
|
|
203
|
+
"button",
|
|
204
|
+
{
|
|
205
|
+
className: "absolute inset-y-0 right-0 w-1 cursor-col-resize hover:bg-border",
|
|
206
|
+
onClick: toggleSidebar,
|
|
207
|
+
"aria-label": "Toggle Sidebar"
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
Sidebar,
|
|
213
|
+
SidebarContent,
|
|
214
|
+
SidebarFooter,
|
|
215
|
+
SidebarGroup,
|
|
216
|
+
SidebarGroupContent,
|
|
217
|
+
SidebarGroupLabel,
|
|
218
|
+
SidebarHeader,
|
|
219
|
+
SidebarInset,
|
|
220
|
+
SidebarMenu,
|
|
221
|
+
SidebarMenuButton,
|
|
222
|
+
SidebarMenuItem,
|
|
223
|
+
SidebarProvider,
|
|
224
|
+
SidebarRail,
|
|
225
|
+
SidebarTrigger,
|
|
226
|
+
useSidebar
|
|
227
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
import { Sheet, SheetContent } from './sheet.js';
|
|
6
|
+
|
|
7
|
+
const SIDEBAR_WIDTH = '16rem';
|
|
8
|
+
const SIDEBAR_WIDTH_ICON = '3rem';
|
|
9
|
+
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
|
10
|
+
const SIDEBAR_COOKIE_NAME = 'sidebar:state';
|
|
11
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
|
12
|
+
|
|
13
|
+
const SidebarContext = createContext(null);
|
|
14
|
+
|
|
15
|
+
export function useSidebar() {
|
|
16
|
+
const context = useContext(SidebarContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error('useSidebar must be used within a SidebarProvider');
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function SidebarProvider({
|
|
24
|
+
children,
|
|
25
|
+
defaultOpen = true,
|
|
26
|
+
open: openProp,
|
|
27
|
+
onOpenChange: setOpenProp,
|
|
28
|
+
}) {
|
|
29
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
30
|
+
const [openMobile, setOpenMobile] = useState(false);
|
|
31
|
+
const [_open, _setOpen] = useState(defaultOpen);
|
|
32
|
+
const open = openProp !== undefined ? openProp : _open;
|
|
33
|
+
const setOpen = useCallback(
|
|
34
|
+
(value) => {
|
|
35
|
+
const newOpen = typeof value === 'function' ? value(open) : value;
|
|
36
|
+
if (setOpenProp) {
|
|
37
|
+
setOpenProp(newOpen);
|
|
38
|
+
} else {
|
|
39
|
+
_setOpen(newOpen);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${newOpen}; path=/; max-age=${60 * 60 * 24 * 7}`;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// SSR safety
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[setOpenProp, open]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const toggleSidebar = useCallback(() => {
|
|
51
|
+
if (isMobile) {
|
|
52
|
+
setOpenMobile((prev) => !prev);
|
|
53
|
+
} else {
|
|
54
|
+
setOpen((prev) => !prev);
|
|
55
|
+
}
|
|
56
|
+
}, [isMobile, setOpen]);
|
|
57
|
+
|
|
58
|
+
// Detect mobile
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const check = () => setIsMobile(window.innerWidth < 768);
|
|
61
|
+
check();
|
|
62
|
+
window.addEventListener('resize', check);
|
|
63
|
+
return () => window.removeEventListener('resize', check);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
// Keyboard shortcut (Cmd/Ctrl + B)
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const handleKeyDown = (e) => {
|
|
69
|
+
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
toggleSidebar();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
75
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
76
|
+
}, [toggleSidebar]);
|
|
77
|
+
|
|
78
|
+
const state = open ? 'expanded' : 'collapsed';
|
|
79
|
+
|
|
80
|
+
const contextValue = useMemo(
|
|
81
|
+
() => ({
|
|
82
|
+
state,
|
|
83
|
+
open,
|
|
84
|
+
setOpen,
|
|
85
|
+
isMobile,
|
|
86
|
+
openMobile,
|
|
87
|
+
setOpenMobile,
|
|
88
|
+
toggleSidebar,
|
|
89
|
+
}),
|
|
90
|
+
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<SidebarContext.Provider value={contextValue}>
|
|
95
|
+
<div
|
|
96
|
+
className="group/sidebar-wrapper flex min-h-svh w-full"
|
|
97
|
+
style={{
|
|
98
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
99
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
100
|
+
'--sidebar-width-mobile': SIDEBAR_WIDTH_MOBILE,
|
|
101
|
+
}}
|
|
102
|
+
data-sidebar-state={state}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</div>
|
|
106
|
+
</SidebarContext.Provider>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function Sidebar({ children, className, side = 'left' }) {
|
|
111
|
+
const { isMobile, open, openMobile, setOpenMobile } = useSidebar();
|
|
112
|
+
|
|
113
|
+
if (isMobile) {
|
|
114
|
+
return (
|
|
115
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile}>
|
|
116
|
+
<SheetContent
|
|
117
|
+
side={side}
|
|
118
|
+
className={cn('w-[var(--sidebar-width-mobile)] p-0 [&>button]:hidden', className)}
|
|
119
|
+
>
|
|
120
|
+
<div className="flex h-full w-full flex-col">{children}</div>
|
|
121
|
+
</SheetContent>
|
|
122
|
+
</Sheet>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
className={cn(
|
|
129
|
+
'sticky top-0 flex h-svh flex-col border-r border-border bg-muted transition-[width] duration-200',
|
|
130
|
+
open ? 'w-[var(--sidebar-width)]' : 'w-[var(--sidebar-width-icon)]',
|
|
131
|
+
className
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<div
|
|
135
|
+
className={cn(
|
|
136
|
+
'flex h-full flex-col overflow-hidden',
|
|
137
|
+
open ? 'w-[var(--sidebar-width)]' : 'w-[var(--sidebar-width-icon)]'
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function SidebarHeader({ children, className }) {
|
|
147
|
+
return <div className={cn('flex flex-col gap-2 p-2', className)}>{children}</div>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function SidebarContent({ children, className }) {
|
|
151
|
+
return (
|
|
152
|
+
<div className={cn('flex min-h-0 flex-1 flex-col overflow-y-auto', className)}>
|
|
153
|
+
{children}
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function SidebarFooter({ children, className }) {
|
|
159
|
+
return <div className={cn('flex flex-col gap-2 p-2', className)}>{children}</div>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function SidebarMenu({ children, className }) {
|
|
163
|
+
return <ul className={cn('flex w-full min-w-0 flex-col gap-1', className)}>{children}</ul>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function SidebarMenuItem({ children, className }) {
|
|
167
|
+
return <li className={cn('group/menu-item relative', className)}>{children}</li>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function SidebarMenuButton({ children, className, isActive, asChild, tooltip, ...props }) {
|
|
171
|
+
const Tag = asChild ? 'span' : 'button';
|
|
172
|
+
return (
|
|
173
|
+
<Tag
|
|
174
|
+
className={cn(
|
|
175
|
+
'flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none transition-colors',
|
|
176
|
+
'hover:bg-background hover:text-foreground',
|
|
177
|
+
isActive && 'bg-background text-foreground font-medium',
|
|
178
|
+
className
|
|
179
|
+
)}
|
|
180
|
+
{...props}
|
|
181
|
+
>
|
|
182
|
+
{children}
|
|
183
|
+
</Tag>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function SidebarGroup({ children, className }) {
|
|
188
|
+
return <div className={cn('relative flex w-full min-w-0 flex-col p-2', className)}>{children}</div>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function SidebarGroupLabel({ children, className }) {
|
|
192
|
+
return (
|
|
193
|
+
<div
|
|
194
|
+
className={cn(
|
|
195
|
+
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground',
|
|
196
|
+
className
|
|
197
|
+
)}
|
|
198
|
+
>
|
|
199
|
+
{children}
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function SidebarGroupContent({ children, className }) {
|
|
205
|
+
return <div className={cn('w-full', className)}>{children}</div>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function SidebarInset({ children, className }) {
|
|
209
|
+
return (
|
|
210
|
+
<main className={cn('relative flex min-h-svh flex-1 flex-col bg-background', className)}>
|
|
211
|
+
{children}
|
|
212
|
+
</main>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function SidebarTrigger({ className, ...props }) {
|
|
217
|
+
const { toggleSidebar } = useSidebar();
|
|
218
|
+
return (
|
|
219
|
+
<button
|
|
220
|
+
className={cn(
|
|
221
|
+
'inline-flex items-center justify-center rounded-md p-2 text-foreground hover:bg-muted',
|
|
222
|
+
className
|
|
223
|
+
)}
|
|
224
|
+
onClick={toggleSidebar}
|
|
225
|
+
{...props}
|
|
226
|
+
>
|
|
227
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="size-4">
|
|
228
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
229
|
+
<path d="M9 3v18" />
|
|
230
|
+
</svg>
|
|
231
|
+
<span className="sr-only">Toggle Sidebar</span>
|
|
232
|
+
</button>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function SidebarRail() {
|
|
237
|
+
const { toggleSidebar } = useSidebar();
|
|
238
|
+
return (
|
|
239
|
+
<button
|
|
240
|
+
className="absolute inset-y-0 right-0 w-1 cursor-col-resize hover:bg-border"
|
|
241
|
+
onClick={toggleSidebar}
|
|
242
|
+
aria-label="Toggle Sidebar"
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect, createContext, useContext, cloneElement } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
const TooltipContext = createContext({ open: false });
|
|
6
|
+
function TooltipProvider({ children, delayDuration = 200 }) {
|
|
7
|
+
return children;
|
|
8
|
+
}
|
|
9
|
+
function Tooltip({ children }) {
|
|
10
|
+
const [open, setOpen] = useState(false);
|
|
11
|
+
const timeoutRef = useRef(null);
|
|
12
|
+
const handleOpen = () => {
|
|
13
|
+
timeoutRef.current = setTimeout(() => setOpen(true), 200);
|
|
14
|
+
};
|
|
15
|
+
const handleClose = () => {
|
|
16
|
+
clearTimeout(timeoutRef.current);
|
|
17
|
+
setOpen(false);
|
|
18
|
+
};
|
|
19
|
+
useEffect(() => () => clearTimeout(timeoutRef.current), []);
|
|
20
|
+
return /* @__PURE__ */ jsx(TooltipContext.Provider, { value: { open, handleOpen, handleClose }, children: /* @__PURE__ */ jsx("div", { className: "relative inline-flex", onMouseEnter: handleOpen, onMouseLeave: handleClose, children }) });
|
|
21
|
+
}
|
|
22
|
+
function TooltipTrigger({ children, asChild }) {
|
|
23
|
+
if (asChild && children) {
|
|
24
|
+
return children;
|
|
25
|
+
}
|
|
26
|
+
return children;
|
|
27
|
+
}
|
|
28
|
+
function TooltipContent({ children, className, align = "center", side = "bottom", ...props }) {
|
|
29
|
+
const { open } = useContext(TooltipContext);
|
|
30
|
+
if (!open) return null;
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"div",
|
|
33
|
+
{
|
|
34
|
+
className: cn(
|
|
35
|
+
"absolute z-50 overflow-hidden rounded-md border border-border bg-muted px-3 py-1.5 text-sm text-foreground shadow-md",
|
|
36
|
+
"animate-in fade-in-0 zoom-in-95",
|
|
37
|
+
side === "bottom" && "top-full mt-1",
|
|
38
|
+
side === "top" && "bottom-full mb-1",
|
|
39
|
+
side === "right" && "left-full ml-1 top-1/2 -translate-y-1/2",
|
|
40
|
+
side === "left" && "right-full mr-1 top-1/2 -translate-y-1/2",
|
|
41
|
+
side !== "right" && side !== "left" && align === "center" && "left-1/2 -translate-x-1/2",
|
|
42
|
+
side !== "right" && side !== "left" && align === "end" && "right-0",
|
|
43
|
+
side !== "right" && side !== "left" && align === "start" && "left-0",
|
|
44
|
+
className
|
|
45
|
+
),
|
|
46
|
+
...props,
|
|
47
|
+
children
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
Tooltip,
|
|
53
|
+
TooltipContent,
|
|
54
|
+
TooltipProvider,
|
|
55
|
+
TooltipTrigger
|
|
56
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, createContext, useContext, cloneElement } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
const TooltipContext = createContext({ open: false });
|
|
7
|
+
|
|
8
|
+
export function TooltipProvider({ children, delayDuration = 200 }) {
|
|
9
|
+
return children;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Tooltip({ children }) {
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
const timeoutRef = useRef(null);
|
|
15
|
+
|
|
16
|
+
const handleOpen = () => {
|
|
17
|
+
timeoutRef.current = setTimeout(() => setOpen(true), 200);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleClose = () => {
|
|
21
|
+
clearTimeout(timeoutRef.current);
|
|
22
|
+
setOpen(false);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
useEffect(() => () => clearTimeout(timeoutRef.current), []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<TooltipContext.Provider value={{ open, handleOpen, handleClose }}>
|
|
29
|
+
<div className="relative inline-flex" onMouseEnter={handleOpen} onMouseLeave={handleClose}>
|
|
30
|
+
{children}
|
|
31
|
+
</div>
|
|
32
|
+
</TooltipContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function TooltipTrigger({ children, asChild }) {
|
|
37
|
+
if (asChild && children) {
|
|
38
|
+
return children;
|
|
39
|
+
}
|
|
40
|
+
return children;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function TooltipContent({ children, className, align = 'center', side = 'bottom', ...props }) {
|
|
44
|
+
const { open } = useContext(TooltipContext);
|
|
45
|
+
if (!open) return null;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
className={cn(
|
|
50
|
+
'absolute z-50 overflow-hidden rounded-md border border-border bg-muted px-3 py-1.5 text-sm text-foreground shadow-md',
|
|
51
|
+
'animate-in fade-in-0 zoom-in-95',
|
|
52
|
+
side === 'bottom' && 'top-full mt-1',
|
|
53
|
+
side === 'top' && 'bottom-full mb-1',
|
|
54
|
+
side === 'right' && 'left-full ml-1 top-1/2 -translate-y-1/2',
|
|
55
|
+
side === 'left' && 'right-full mr-1 top-1/2 -translate-y-1/2',
|
|
56
|
+
side !== 'right' && side !== 'left' && align === 'center' && 'left-1/2 -translate-x-1/2',
|
|
57
|
+
side !== 'right' && side !== 'left' && align === 'end' && 'right-0',
|
|
58
|
+
side !== 'right' && side !== 'left' && align === 'start' && 'left-0',
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|