Unverified 提交 7910e2ee authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Replace button title attributes with shadcn Tooltip components (#2470)

## Summary - Replace native HTML `title` attributes on button elements across 25 files with proper `<Tooltip>` / `<TooltipTrigger>` / `<TooltipContent>` components from the existing UI library - Native browser tooltips are delayed and unstyled; the shadcn tooltips provide a consistent, polished tooltip experience with animations and proper positioning - Only button/interactive elements were converted; `title` on non-interactive elements like `<span>` (text overflow), `<iframe>`, and component props were intentionally left as-is ## Test plan - [x] TypeScript type check passes (`npm run ts`) - [x] Lint passes (`npm run lint`) - [x] Formatting passes (`npm run fmt`) - [x] All 661 unit tests pass (`npm test`) - [ ] Manual: hover over icon buttons throughout the app (chat input, preview toolbar, annotator, settings) to verify styled tooltips appear instead of browser-native ones 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2470"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Primarily UI/UX refactoring of hover tooltips with minimal behavioral impact; main risk is small layout/accessibility regressions from the updated tooltip styling/positioning. > > **Overview** > Replaces native `title` tooltips on many interactive controls (chat, preview/annotator toolbars, settings/connectors, branch manager, etc.) with the app’s `Tooltip` components, using `TooltipTrigger`’s `render` prop to avoid invalid nested `<button>` markup. > > Updates the tooltip implementation (`src/components/ui/tooltip.tsx`) to remove the implicit provider wrapper, tweak default positioning (`sideOffset`), and restyle tooltip/arrow classes; adjusts E2E aria snapshots accordingly and documents the `TooltipTrigger render` pitfall in `AGENTS.md`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8055379edcec73b53ca54ee82b9f4fb4a2f92c63. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Replaced native title tooltips on interactive buttons with shadcn Tooltip for consistent, styled, and responsive tooltips across the app. Improves usability and visual polish without changing button behavior. - **Refactors** - Converted titles to Tooltip/TooltipTrigger/TooltipContent across ~25 files, using TooltipTrigger’s render prop to avoid nested button HTML. - Kept native title for drag handles and resize rails, and left titles on non-interactive elements (e.g., span, iframe, component props). - Updated tooltip defaults (sideOffset 4) and removed the implicit provider; added aria-labels to icon-only buttons; fixed ToggleGroupItem corner styles and preserved ref forwarding where needed. <sup>Written for commit 8055379edcec73b53ca54ee82b9f4fb4a2f92c63. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 fb615c33
...@@ -214,3 +214,20 @@ Add `#skip-bugbot` to the PR description for trivial PRs that won't affect end-u ...@@ -214,3 +214,20 @@ Add `#skip-bugbot` to the PR description for trivial PRs that won't affect end-u
- Linting or test setup changes - Linting or test setup changes
- Documentation-only changes - Documentation-only changes
- CI/build configuration updates - CI/build configuration updates
## Learnings
### TooltipTrigger render prop (Base UI)
- `TooltipTrigger` from `@base-ui/react/tooltip` (wrapped in `src/components/ui/tooltip.tsx`) renders a `<button>` by default. Wrapping another button-like element (`<button>`, `<Button>`, `<DropdownMenuTrigger>`, `<PopoverTrigger>`, `<MiniSelectTrigger>`, `<ToggleGroupItem>`) inside it creates invalid nested `<button>` HTML. Use the `render` prop instead:
```tsx
// Wrong: nested buttons
<TooltipTrigger><Button onClick={fn}>Click</Button></TooltipTrigger>
// Correct: render prop merges into a single element
<TooltipTrigger render={<Button onClick={fn} />}>Click</TooltipTrigger>
```
- Wrapping `ToggleGroupItem` in `TooltipTrigger` without `render` also breaks `:first-child`/`:last-child` CSS selectors for rounded corners on the group.
- For drag handles and resize rails, prefer the native `title` attribute over `Tooltip` — tooltips appear immediately on hover and interfere with drag interactions, while `title` has a built-in delay.
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/mcp-calculator - paragraph: tc=local-agent/mcp-calculator
- paragraph: I'll calculate the sum of 5 and 3 using the calculator. - paragraph: I'll calculate the sum of 5 and 3 using the calculator.
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
...@@ -14,20 +14,20 @@ ...@@ -14,20 +14,20 @@
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/ask-read-file - paragraph: tc=local-agent/ask-read-file
- paragraph: Let me read the file to explain its contents. - paragraph: Let me read the file to explain its contents.
- img - img
- text: App.tsx Read src/App.tsx - text: App.tsx Read src/App.tsx
- paragraph: This is a simple React component that renders a div with the text 'Minimal imported app'. The component is exported as the default export. - paragraph: This is a simple React component that renders a div with the text 'Minimal imported app'. The component is exported as the default export.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,11 +6,15 @@ ...@@ -6,11 +6,15 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/parallel-tools - paragraph: tc=local-agent/parallel-tools
- paragraph: I'll create two files for you in parallel. - paragraph: I'll create two files for you in parallel.
...@@ -27,11 +31,15 @@ ...@@ -27,11 +31,15 @@
- img - img
- text: "src/utils/string.ts Summary: Create string utilities" - text: "src/utils/string.ts Summary: Create string utilities"
- paragraph: Task completed. - paragraph: Task completed.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/read-then-edit - paragraph: tc=local-agent/read-then-edit
- paragraph: Let me first read the current file contents to understand what we're working with. - paragraph: Let me first read the current file contents to understand what we're working with.
...@@ -26,13 +26,13 @@ ...@@ -26,13 +26,13 @@
- img - img
- text: src/App.tsx - text: src/App.tsx
- paragraph: Done! I've updated the title from 'Minimal imported app' to 'UPDATED imported app'. The change has been applied successfully. - paragraph: Done! I've updated the title from 'Minimal imported app' to 'UPDATED imported app'. The change has been applied successfully.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,13 +6,15 @@ ...@@ -6,13 +6,15 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/code-search - paragraph: tc=local-agent/code-search
- paragraph: I'll search for files related to React components in the codebase. - paragraph: I'll search for files related to React components in the codebase.
...@@ -20,13 +22,13 @@ ...@@ -20,13 +22,13 @@
- img - img
- img - img
- paragraph: I found the relevant files! The main React component is in src/App.tsx which handles the app rendering. - paragraph: I found the relevant files! The main React component is in src/App.tsx which handles the app rendering.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,22 +6,30 @@ ...@@ -6,22 +6,30 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
- img - img
- text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want. - text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want.
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -6,22 +6,30 @@ ...@@ -6,22 +6,30 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
- img - img
- text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want. - text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want.
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -6,11 +6,15 @@ ...@@ -6,11 +6,15 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
...@@ -24,11 +28,15 @@ ...@@ -24,11 +28,15 @@
- button "Fix with AI": - button "Fix with AI":
- img - img
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -6,13 +6,15 @@ ...@@ -6,13 +6,15 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/upload-to-codebase - paragraph: tc=local-agent/upload-to-codebase
- paragraph: "Attachments:" - paragraph: "Attachments:"
...@@ -25,13 +27,13 @@ ...@@ -25,13 +27,13 @@
- img - img
- text: "assets/uploaded-file.png Summary: Upload file to codebase" - text: "assets/uploaded-file.png Summary: Upload file to codebase"
- paragraph: I've successfully uploaded your file to assets/uploaded-file.png in the codebase. - paragraph: I've successfully uploaded your file to assets/uploaded-file.png in the codebase.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,31 +6,43 @@ ...@@ -6,31 +6,43 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/grep-search - paragraph: tc=local-agent/grep-search
- paragraph: I'll search for 'createRoot' in the codebase to find where the React app is initialized. - paragraph: I'll search for 'createRoot' in the codebase to find where the React app is initialized.
- img - img
- text: GREP"createRoot"(2 matches) - text: GREP"createRoot"(2 matches)
- img - img
- text: log
- button "Copy":
- img
- text: log
- code: "src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);"
- paragraph: Now I'll search specifically in .tsx files for 'App' to find component references. - paragraph: Now I'll search specifically in .tsx files for 'App' to find component references.
- img - img
- text: GREP"App" in *.tsx(4 matches) - text: GREP"App" in *.tsx(4 matches)
- img - img
- text: log
- button "Copy":
- img
- text: log
- code: "src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />); src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App;"
- paragraph: I found the matches! The React app is initialized in src/main.tsx using createRoot, and the App component is defined in src/App.tsx and imported in src/main.tsx. - paragraph: I found the matches! The React app is initialized in src/main.tsx using createRoot, and the App component is defined in src/App.tsx and imported in src/main.tsx.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,18 +6,20 @@ ...@@ -6,18 +6,20 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/read-logs - paragraph: tc=local-agent/read-logs
- paragraph: Let me check the recent console logs to see what's happening in the application. - paragraph: Let me check the recent console logs to see what's happening in the application.
- img - img
- text: /LOGSReading \d+ logs/ - text: LOGSReading 8 logs
- img - img
- paragraph: Now let me filter for only error logs to identify any issues. - paragraph: Now let me filter for only error logs to identify any issues.
- img - img
...@@ -28,13 +30,13 @@ ...@@ -28,13 +30,13 @@
- text: "LOGSReading 0 logs (type: client)" - text: "LOGSReading 0 logs (type: client)"
- img - img
- paragraph: I've reviewed the console logs. The application appears to be running normally with no critical errors detected. - paragraph: I've reviewed the console logs. The application appears to be running normally with no critical errors detected.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=local-agent/search-replace - paragraph: tc=local-agent/search-replace
- paragraph: Let me first read the file to see its contents. - paragraph: Let me first read the file to see its contents.
...@@ -26,13 +26,13 @@ ...@@ -26,13 +26,13 @@
- img - img
- text: src/App.tsx - text: src/App.tsx
- paragraph: Done! I've updated the message using search_replace. The edit was applied successfully. - paragraph: Done! I've updated the message using search_replace. The edit was applied successfully.
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
- img - img
- text: file1.txt - text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: claude-opus-4-5 - text: claude-opus-4-5
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- button "Undo": - button "Undo":
- img - img
......
- paragraph: "[dump]" - paragraph: "[dump]"
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -3,13 +3,15 @@ ...@@ -3,13 +3,15 @@
- img - img
- text: Index.tsx Read src/pages/Index.tsx - text: Index.tsx Read src/pages/Index.tsx
- paragraph: Done. - paragraph: Done.
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=update-index-1 - paragraph: tc=update-index-1
- paragraph: First read - paragraph: First read
...@@ -19,38 +21,46 @@ ...@@ -19,38 +21,46 @@
- img - img
- img - img
- text: "src/pages/Index.tsx Summary: replace file" - text: "src/pages/Index.tsx Summary: replace file"
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- img - img
- text: wrote 1 file(s) - text: wrote 1 file(s)
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: tc=read-index - paragraph: tc=read-index
- paragraph: "Read the index page:" - paragraph: "Read the index page:"
- img - img
- text: Index.tsx Read src/pages/Index.tsx - text: Index.tsx Read src/pages/Index.tsx
- paragraph: Done. - paragraph: Done.
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img - img
- paragraph: "[dump]" - paragraph: "[dump]"
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -5,11 +5,15 @@ ...@@ -5,11 +5,15 @@
- img - img
- text: src/pages/Index.tsx - text: src/pages/Index.tsx
- paragraph: End of turbo edit - paragraph: End of turbo edit
- button: - button "Copy":
- img - img
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -9,17 +9,31 @@ ...@@ -9,17 +9,31 @@
- text: Warning Could not apply Turbo Edits properly for some of the files; re-generating code...... - text: Warning Could not apply Turbo Edits properly for some of the files; re-generating code......
- img - img
- img - img
- text: Index.tsx Read src/pages/Index.tsx
- img
- text: Search & Replace Index.tsx
- img
- text: src/pages/Index.tsx
- paragraph: "[[dyad-dump-path=*]]"
- img
- text: Warning Could not apply Turbo Edits properly for some of the files; re-generating code......
- img
- img
- text: Index.tsx - text: Index.tsx
- button "Edit": - button "Edit":
- img - img
- img - img
- text: "src/pages/Index.tsx Summary: Rewrite file." - text: "src/pages/Index.tsx Summary: Rewrite file."
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button: - button "Copy":
- img - img
- img - img
- text: auto
- img
- text: less than a minute ago - text: less than a minute ago
- button "Request ID": - button "Copy Request ID":
- img
- button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
...@@ -5,6 +5,11 @@ import { ...@@ -5,6 +5,11 @@ import {
SelectItem, SelectItem,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota"; import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota";
import type { ChatMode } from "@/lib/schemas"; import type { ChatMode } from "@/lib/schemas";
...@@ -91,9 +96,11 @@ export function ChatModeSelector() { ...@@ -91,9 +96,11 @@ export function ChatModeSelector() {
value={selectedMode} value={selectedMode}
onValueChange={(v) => v && handleModeChange(v)} onValueChange={(v) => v && handleModeChange(v)}
> >
<Tooltip>
<TooltipTrigger
render={
<MiniSelectTrigger <MiniSelectTrigger
data-testid="chat-mode-selector" data-testid="chat-mode-selector"
title={`Open mode menu (${isMac ? "⌘ + ." : "Ctrl + ."} to toggle)`}
className={cn( className={cn(
"h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5", "h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5",
selectedMode === "build" || selectedMode === "local-agent" selectedMode === "build" || selectedMode === "local-agent"
...@@ -101,9 +108,15 @@ export function ChatModeSelector() { ...@@ -101,9 +108,15 @@ export function ChatModeSelector() {
: "bg-primary/10 hover:bg-primary/20 focus:bg-primary/20 text-primary border-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 dark:focus:bg-primary/30", : "bg-primary/10 hover:bg-primary/20 focus:bg-primary/20 text-primary border-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 dark:focus:bg-primary/30",
)} )}
size="sm" size="sm"
/>
}
> >
<SelectValue>{getModeDisplayName(selectedMode)}</SelectValue> <SelectValue>{getModeDisplayName(selectedMode)}</SelectValue>
</MiniSelectTrigger> </TooltipTrigger>
<TooltipContent>
{`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`}
</TooltipContent>
</Tooltip>
<SelectContent align="start"> <SelectContent align="start">
{isProEnabled && ( {isProEnabled && (
<SelectItem value="local-agent"> <SelectItem value="local-agent">
......
...@@ -14,6 +14,11 @@ import { VersionPane } from "./chat/VersionPane"; ...@@ -14,6 +14,11 @@ import { VersionPane } from "./chat/VersionPane";
import { ChatError } from "./chat/ChatError"; import { ChatError } from "./chat/ChatError";
import { FreeAgentQuotaBanner } from "./chat/FreeAgentQuotaBanner"; import { FreeAgentQuotaBanner } from "./chat/FreeAgentQuotaBanner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { ArrowDown } from "lucide-react"; import { ArrowDown } from "lucide-react";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota"; import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota";
...@@ -166,15 +171,21 @@ export function ChatPanel({ ...@@ -166,15 +171,21 @@ export function ChatPanel({
{/* Scroll to bottom button */} {/* Scroll to bottom button */}
{showScrollButton && ( {showScrollButton && (
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-10"> <div className="absolute bottom-6 left-1/2 -translate-x-1/2 z-10">
<Tooltip>
<TooltipTrigger
render={
<Button <Button
onClick={handleScrollButtonClick} onClick={handleScrollButtonClick}
size="icon" size="icon"
className="rounded-full shadow-lg hover:shadow-xl transition-all border border-border/50 backdrop-blur-sm bg-background/95 hover:bg-accent" className="rounded-full shadow-lg hover:shadow-xl transition-all border border-border/50 backdrop-blur-sm bg-background/95 hover:bg-accent"
variant="outline" variant="outline"
title={"Scroll to bottom"} />
}
> >
<ArrowDown className="h-4 w-4" /> <ArrowDown className="h-4 w-4" />
</Button> </TooltipTrigger>
<TooltipContent>Scroll to bottom</TooltipContent>
</Tooltip>
</div> </div>
)} )}
</div> </div>
......
import { Copy, Check } from "lucide-react"; import { Copy, Check } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface CopyErrorMessageProps { interface CopyErrorMessageProps {
errorMessage: string; errorMessage: string;
...@@ -24,6 +29,9 @@ export const CopyErrorMessage = ({ ...@@ -24,6 +29,9 @@ export const CopyErrorMessage = ({
}; };
return ( return (
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleCopy} onClick={handleCopy}
className={`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs transition-colors ${
...@@ -31,7 +39,8 @@ export const CopyErrorMessage = ({ ...@@ -31,7 +39,8 @@ export const CopyErrorMessage = ({
? "bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300" ? "bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300"
: "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600" : "bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"
} ${className}`} } ${className}`}
title={isCopied ? "Copied!" : "Copy error message"} />
}
> >
{isCopied ? ( {isCopied ? (
<> <>
...@@ -44,6 +53,10 @@ export const CopyErrorMessage = ({ ...@@ -44,6 +53,10 @@ export const CopyErrorMessage = ({
<span>Copy</span> <span>Copy</span>
</> </>
)} )}
</button> </TooltipTrigger>
<TooltipContent>
{isCopied ? "Copied!" : "Copy error message"}
</TooltipContent>
</Tooltip>
); );
}; };
import React from "react"; import React from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { X, Copy, Check } from "lucide-react"; import { X, Copy, Check } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface CustomErrorToastProps { interface CustomErrorToastProps {
message: string; message: string;
...@@ -41,13 +46,14 @@ export function CustomErrorToast({ ...@@ -41,13 +46,14 @@ export function CustomErrorToast({
{/* Action buttons */} {/* Action buttons */}
<div className="flex items-center space-x-1.5 ml-auto"> <div className="flex items-center space-x-1.5 ml-auto">
<Tooltip>
<TooltipTrigger>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleCopy(); handleCopy();
}} }}
className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-100/70 rounded-lg transition-all duration-150" className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-100/70 rounded-lg transition-all duration-150"
title="Copy to clipboard"
> >
{copied ? ( {copied ? (
<Check className="w-4 h-4 text-green-500" /> <Check className="w-4 h-4 text-green-500" />
...@@ -55,16 +61,23 @@ export function CustomErrorToast({ ...@@ -55,16 +61,23 @@ export function CustomErrorToast({
<Copy className="w-4 h-4" /> <Copy className="w-4 h-4" />
)} )}
</button> </button>
</TooltipTrigger>
<TooltipContent>Copy to clipboard</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleClose(); handleClose();
}} }}
className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-100/70 rounded-lg transition-all duration-150" className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-100/70 rounded-lg transition-all duration-150"
title="Close"
> >
<X className="w-4 h-4" /> <X className="w-4 h-4" />
</button> </button>
</TooltipTrigger>
<TooltipContent>Close</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
<div> <div>
......
...@@ -34,6 +34,11 @@ import { ...@@ -34,6 +34,11 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
...@@ -414,14 +419,22 @@ export function GithubBranchManager({ ...@@ -414,14 +419,22 @@ export function GithubBranchManager({
</Select> </Select>
<DropdownMenu> <DropdownMenu>
<Tooltip>
<TooltipTrigger
render={
<DropdownMenuTrigger <DropdownMenuTrigger
className={cn(buttonVariants({ variant: "outline", size: "icon" }))} className={cn(
title="Branch actions" buttonVariants({ variant: "outline", size: "icon" }),
)}
aria-label="Branch actions" aria-label="Branch actions"
data-testid="branch-actions-menu-trigger" data-testid="branch-actions-menu-trigger"
/>
}
> >
<EllipsisVertical className="h-4 w-4" /> <EllipsisVertical className="h-4 w-4" />
</DropdownMenuTrigger> </TooltipTrigger>
<TooltipContent>Branch actions</TooltipContent>
</Tooltip>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem <DropdownMenuItem
onClick={() => setShowCreateDialog(true)} onClick={() => setShowCreateDialog(true)}
......
...@@ -7,6 +7,11 @@ import { ...@@ -7,6 +7,11 @@ import {
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Sparkles, Info } from "lucide-react"; import { Sparkles, Info } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { ipc } from "@/ipc/types"; import { ipc } from "@/ipc/types";
import { hasDyadProKey, type UserSettings } from "@/lib/schemas"; import { hasDyadProKey, type UserSettings } from "@/lib/schemas";
...@@ -57,13 +62,17 @@ export function ProModeSelector() { ...@@ -57,13 +62,17 @@ export function ProModeSelector() {
return ( return (
<Popover> <Popover>
<PopoverTrigger <Tooltip>
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-primary/50 bg-background shadow-sm hover:bg-primary/10 h-8 px-1.5 gap-1.5 shadow-primary/10 hover:shadow-md hover:shadow-primary/15" <TooltipTrigger
title="Configure Dyad Pro settings" render={
<PopoverTrigger className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-primary/50 bg-background shadow-sm hover:bg-primary/10 h-8 px-1.5 gap-1.5 shadow-primary/10 hover:shadow-md hover:shadow-primary/15" />
}
> >
<Sparkles className="h-4 w-4 text-primary" /> <Sparkles className="h-4 w-4 text-primary" />
<span className="text-primary font-medium text-xs-sm">Pro</span> <span className="text-primary font-medium text-xs-sm">Pro</span>
</PopoverTrigger> </TooltipTrigger>
<TooltipContent>Configure Dyad Pro settings</TooltipContent>
</Tooltip>
<PopoverContent className="w-80 border-primary/20"> <PopoverContent className="w-80 border-primary/20">
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-1"> <div className="space-y-1">
...@@ -205,36 +214,58 @@ function TurboEditsSelector({ ...@@ -205,36 +214,58 @@ function TurboEditsSelector({
className="inline-flex rounded-md border border-input" className="inline-flex rounded-md border border-input"
data-testid="turbo-edits-selector" data-testid="turbo-edits-selector"
> >
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "off" ? "default" : "ghost"} variant={currentValue === "off" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("off")} onClick={() => onValueChange("off")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-r-none border-r border-input h-8 px-3 text-xs flex-shrink-0" className="rounded-r-none border-r border-input h-8 px-3 text-xs flex-shrink-0"
title="Disable Turbo Edits" />
}
> >
Off Off
</Button> </TooltipTrigger>
<TooltipContent>Disable Turbo Edits</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "v1" ? "default" : "ghost"} variant={currentValue === "v1" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("v1")} onClick={() => onValueChange("v1")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-none border-r border-input h-8 px-3 text-xs flex-shrink-0" className="rounded-none border-r border-input h-8 px-3 text-xs flex-shrink-0"
title="Uses a smaller model to complete edits" />
}
> >
Classic Classic
</Button> </TooltipTrigger>
<TooltipContent>
Uses a smaller model to complete edits
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "v2" ? "default" : "ghost"} variant={currentValue === "v2" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("v2")} onClick={() => onValueChange("v2")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-l-none h-8 px-3 text-xs flex-shrink-0" className="rounded-l-none h-8 px-3 text-xs flex-shrink-0"
title="Find and replaces specific text blocks" />
}
> >
Search & replace Search & replace
</Button> </TooltipTrigger>
<TooltipContent>
Find and replaces specific text blocks
</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
); );
...@@ -282,36 +313,59 @@ function SmartContextSelector({ ...@@ -282,36 +313,59 @@ function SmartContextSelector({
className="inline-flex rounded-md border border-input" className="inline-flex rounded-md border border-input"
data-testid="smart-context-selector" data-testid="smart-context-selector"
> >
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "off" ? "default" : "ghost"} variant={currentValue === "off" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("off")} onClick={() => onValueChange("off")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-r-none border-r border-input h-8 px-3 text-xs flex-shrink-0" className="rounded-r-none border-r border-input h-8 px-3 text-xs flex-shrink-0"
title="Disable Smart Context" />
}
> >
Off Off
</Button> </TooltipTrigger>
<TooltipContent>Disable Smart Context</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "balanced" ? "default" : "ghost"} variant={currentValue === "balanced" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("balanced")} onClick={() => onValueChange("balanced")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-none border-r border-input h-8 px-3 text-xs flex-shrink-0" className="rounded-none border-r border-input h-8 px-3 text-xs flex-shrink-0"
title="Selects most relevant files with balanced context size" />
}
> >
Balanced Balanced
</Button> </TooltipTrigger>
<TooltipContent>
Selects most relevant files with balanced context size
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant={currentValue === "deep" ? "default" : "ghost"} variant={currentValue === "deep" ? "default" : "ghost"}
size="sm" size="sm"
onClick={() => onValueChange("deep")} onClick={() => onValueChange("deep")}
disabled={!isTogglable} disabled={!isTogglable}
className="rounded-l-none h-8 px-3 text-xs flex-shrink-0" className="rounded-l-none h-8 px-3 text-xs flex-shrink-0"
title="Experimental: Keeps full conversation history for maximum context and cache-optimized to control costs" />
}
> >
Deep Deep
</Button> </TooltipTrigger>
<TooltipContent>
Experimental: Keeps full conversation history for maximum context
and cache-optimized to control costs
</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
); );
......
...@@ -17,6 +17,11 @@ import { AlertTriangle } from "lucide-react"; ...@@ -17,6 +17,11 @@ import { AlertTriangle } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
...@@ -123,26 +128,38 @@ export function ProviderSettingsGrid() { ...@@ -123,26 +128,38 @@ export function ProviderSettingsGrid() {
className="flex items-center justify-end" className="flex items-center justify-end"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<Tooltip>
<TooltipTrigger
render={
<Button <Button
data-testid="edit-custom-provider" data-testid="edit-custom-provider"
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-8 w-8 p-0 hover:bg-muted rounded-md" className="h-8 w-8 p-0 hover:bg-muted rounded-md"
title="Edit Provider"
onClick={() => handleEditProvider(provider)} onClick={() => handleEditProvider(provider)}
/>
}
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</Button> </TooltipTrigger>
<TooltipContent>Edit Provider</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
data-testid="delete-custom-provider" data-testid="delete-custom-provider"
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-8 w-8 p-0 text-destructive hover:text-destructive hover:bg-destructive/10 rounded-md" className="h-8 w-8 p-0 text-destructive hover:text-destructive hover:bg-destructive/10 rounded-md"
title="Delete Provider"
onClick={() => setProviderToDelete(provider.id)} onClick={() => setProviderToDelete(provider.id)}
/>
}
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</Button> </TooltipTrigger>
<TooltipContent>Delete Provider</TooltipContent>
</Tooltip>
</div> </div>
)} )}
<CardTitle className="text-lg font-medium mb-2"> <CardTitle className="text-lg font-medium mb-2">
......
...@@ -38,6 +38,11 @@ import connectSupabaseDark from "../../assets/supabase/connect-supabase-dark.svg ...@@ -38,6 +38,11 @@ import connectSupabaseDark from "../../assets/supabase/connect-supabase-dark.svg
import connectSupabaseLight from "../../assets/supabase/connect-supabase-light.svg"; import connectSupabaseLight from "../../assets/supabase/connect-supabase-light.svg";
import { ExternalLink, Plus, RefreshCw, Trash2 } from "lucide-react"; import { ExternalLink, Plus, RefreshCw, Trash2 } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useTheme } from "@/contexts/ThemeContext"; import { useTheme } from "@/contexts/ThemeContext";
import { isSupabaseConnected } from "@/lib/schemas"; import { isSupabaseConnected } from "@/lib/schemas";
...@@ -268,17 +273,23 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -268,17 +273,23 @@ export function SupabaseConnector({ appId }: { appId: number }) {
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
Supabase Projects Supabase Projects
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={() => refetchProjects()} onClick={() => refetchProjects()}
disabled={isFetchingProjects} disabled={isFetchingProjects}
title="Refresh projects" />
}
> >
<RefreshCw <RefreshCw
className={`h-4 w-4 ${isFetchingProjects ? "animate-spin" : ""}`} className={`h-4 w-4 ${isFetchingProjects ? "animate-spin" : ""}`}
/> />
</Button> </TooltipTrigger>
<TooltipContent>Refresh projects</TooltipContent>
</Tooltip>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
...@@ -333,6 +344,9 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -333,6 +344,9 @@ export function SupabaseConnector({ appId }: { appId: number }) {
</span> </span>
)} )}
</div> </div>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
...@@ -340,11 +354,14 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -340,11 +354,14 @@ export function SupabaseConnector({ appId }: { appId: number }) {
onClick={() => onClick={() =>
handleDeleteOrganization(org.organizationSlug) handleDeleteOrganization(org.organizationSlug)
} }
title="Disconnect organization" />
}
> >
<Trash2 className="h-3.5 w-3.5 mr-1" /> <Trash2 className="h-3.5 w-3.5 mr-1" />
<span className="text-xs">Disconnect</span> <span className="text-xs">Disconnect</span>
</Button> </TooltipTrigger>
<TooltipContent>Disconnect organization</TooltipContent>
</Tooltip>
</div> </div>
))} ))}
</div> </div>
......
...@@ -5,6 +5,11 @@ import { Label } from "@/components/ui/label"; ...@@ -5,6 +5,11 @@ import { Label } from "@/components/ui/label";
// We might need a Supabase icon here, but for now, let's use a generic one or text. // We might need a Supabase icon here, but for now, let's use a generic one or text.
// import { Supabase } from "lucide-react"; // Placeholder // import { Supabase } from "lucide-react"; // Placeholder
import { DatabaseZap, Trash2 } from "lucide-react"; // Using DatabaseZap as a placeholder import { DatabaseZap, Trash2 } from "lucide-react"; // Using DatabaseZap as a placeholder
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useSupabase } from "@/hooks/useSupabase"; import { useSupabase } from "@/hooks/useSupabase";
import { showSuccess, showError } from "@/lib/toast"; import { showSuccess, showError } from "@/lib/toast";
...@@ -120,16 +125,24 @@ export function SupabaseIntegration() { ...@@ -120,16 +125,24 @@ export function SupabaseIntegration() {
</span> </span>
)} )}
</div> </div>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-7 px-2 text-muted-foreground hover:text-destructive shrink-0" className="h-7 px-2 text-muted-foreground hover:text-destructive shrink-0"
onClick={() => handleDeleteOrganization(org.organizationSlug)} onClick={() =>
title="Disconnect organization" handleDeleteOrganization(org.organizationSlug)
}
/>
}
> >
<Trash2 className="h-3.5 w-3.5 mr-1" /> <Trash2 className="h-3.5 w-3.5 mr-1" />
<span className="text-xs">Disconnect</span> <span className="text-xs">Disconnect</span>
</Button> </TooltipTrigger>
<TooltipContent>Disconnect organization</TooltipContent>
</Tooltip>
</div> </div>
))} ))}
</div> </div>
......
...@@ -75,6 +75,11 @@ import { VisualEditingChangesDialog } from "@/components/preview_panel/VisualEdi ...@@ -75,6 +75,11 @@ import { VisualEditingChangesDialog } from "@/components/preview_panel/VisualEdi
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/queryKeys"; import { queryKeys } from "@/lib/queryKeys";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { import {
ContextLimitBanner, ContextLimitBanner,
shouldShowContextLimitBanner, shouldShowContextLimitBanner,
...@@ -467,16 +472,25 @@ export function ChatInput({ chatId }: { chatId?: number }) { ...@@ -467,16 +472,25 @@ export function ChatInput({ chatId }: { chatId?: number }) {
) : ( ) : (
selectedComponents.length > 0 && ( selectedComponents.length > 0 && (
<div className="border-b border-border p-3 bg-muted/30"> <div className="border-b border-border p-3 bg-muted/30">
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => { onClick={() => {
ipc.system.openExternalUrl("https://dyad.sh/pro"); ipc.system.openExternalUrl("https://dyad.sh/pro");
}} }}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors cursor-pointer" className="flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors cursor-pointer"
title="Visual editing lets you make UI changes without AI and is a Pro-only feature" />
}
> >
<Lock size={16} /> <Lock size={16} />
<span className="font-medium">Visual editor (Pro)</span> <span className="font-medium">Visual editor (Pro)</span>
</button> </TooltipTrigger>
<TooltipContent>
Visual editing lets you make UI changes without AI and is a
Pro-only feature
</TooltipContent>
</Tooltip>
</div> </div>
) )
)} )}
...@@ -505,25 +519,39 @@ export function ChatInput({ chatId }: { chatId?: number }) { ...@@ -505,25 +519,39 @@ export function ChatInput({ chatId }: { chatId?: number }) {
/> />
{isStreaming ? ( {isStreaming ? (
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleCancel} onClick={handleCancel}
aria-label="Cancel generation"
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg" className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
title="Cancel generation" />
}
> >
<StopCircleIcon size={20} /> <StopCircleIcon size={20} />
</button> </TooltipTrigger>
<TooltipContent>Cancel generation</TooltipContent>
</Tooltip>
) : ( ) : (
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={ disabled={
(!inputValue.trim() && attachments.length === 0) || (!inputValue.trim() && attachments.length === 0) ||
disableSendButton disableSendButton
} }
aria-label="Send message"
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50" className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
title="Send message" />
}
> >
<SendHorizontalIcon size={20} /> <SendHorizontalIcon size={20} />
</button> </TooltipTrigger>
<TooltipContent>Send message</TooltipContent>
</Tooltip>
)} )}
</div> </div>
<div className="pl-2 pr-1 flex items-center justify-between pb-2"> <div className="pl-2 pr-1 flex items-center justify-between pb-2">
...@@ -557,15 +585,21 @@ function SuggestionButton({ ...@@ -557,15 +585,21 @@ function SuggestionButton({
}) { }) {
const { isStreaming } = useStreamChat(); const { isStreaming } = useStreamChat();
return ( return (
<Tooltip>
<TooltipTrigger
render={
<Button <Button
disabled={isStreaming} disabled={isStreaming}
variant="outline" variant="outline"
size="sm" size="sm"
onClick={onClick} onClick={onClick}
title={tooltipText} />
}
> >
{children} {children}
</Button> </TooltipTrigger>
<TooltipContent>{tooltipText}</TooltipContent>
</Tooltip>
); );
} }
......
...@@ -21,6 +21,11 @@ import { useAtomValue } from "jotai"; ...@@ -21,6 +21,11 @@ import { useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"; import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface ChatMessageProps { interface ChatMessageProps {
message: Message; message: Message;
...@@ -122,11 +127,16 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -122,11 +127,16 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
{message.role === "assistant" && {message.role === "assistant" &&
message.content && message.content &&
!isStreaming && ( !isStreaming && (
<Tooltip>
<TooltipTrigger
render={
<button <button
data-testid="copy-message-button" data-testid="copy-message-button"
onClick={handleCopyFormatted} onClick={handleCopyFormatted}
title={copied ? "Copied!" : "Copy"} aria-label="Copy"
className="flex items-center space-x-1 px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors duration-200 cursor-pointer" className="flex items-center space-x-1 px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors duration-200 cursor-pointer"
/>
}
> >
{copied ? ( {copied ? (
<Check className="h-4 w-4 text-green-500" /> <Check className="h-4 w-4 text-green-500" />
...@@ -134,7 +144,11 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -134,7 +144,11 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
<Copy className="h-4 w-4" /> <Copy className="h-4 w-4" />
)} )}
<span className="hidden sm:inline"></span> <span className="hidden sm:inline"></span>
</button> </TooltipTrigger>
<TooltipContent>
{copied ? "Copied!" : "Copy"}
</TooltipContent>
</Tooltip>
)} )}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{message.approvalState && ( {message.approvalState && (
...@@ -187,6 +201,9 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -187,6 +201,9 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
</div> </div>
)} )}
{message.requestId && ( {message.requestId && (
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => { onClick={() => {
if (!message.requestId) return; if (!message.requestId) return;
...@@ -206,12 +223,10 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -206,12 +223,10 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
// noop // noop
}); });
}} }}
title={ aria-label="Copy Request ID"
copiedRequestId
? "Copied!"
: `Copy Request ID: ${message.requestId.slice(0, 8)}...`
}
className="flex items-center space-x-1 px-1 py-0.5 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors duration-200 cursor-pointer" className="flex items-center space-x-1 px-1 py-0.5 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors duration-200 cursor-pointer"
/>
}
> >
{copiedRequestId ? ( {copiedRequestId ? (
<Check className="h-3 w-3 text-green-500" /> <Check className="h-3 w-3 text-green-500" />
...@@ -221,7 +236,13 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -221,7 +236,13 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
<span className="text-xs"> <span className="text-xs">
{copiedRequestId ? "Copied" : "Request ID"} {copiedRequestId ? "Copied" : "Request ID"}
</span> </span>
</button> </TooltipTrigger>
<TooltipContent>
{copiedRequestId
? "Copied!"
: `Copy Request ID: ${message.requestId.slice(0, 8)}...`}
</TooltipContent>
</Tooltip>
)} )}
{isLastMessage && message.totalTokens && ( {isLastMessage && message.totalTokens && (
<div <div
......
import { AlertTriangle, ArrowRight } from "lucide-react"; import { AlertTriangle, ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useSummarizeInNewChat } from "./SummarizeInNewChatButton"; import { useSummarizeInNewChat } from "./SummarizeInNewChatButton";
const CONTEXT_LIMIT_THRESHOLD = 40_000; const CONTEXT_LIMIT_THRESHOLD = 40_000;
...@@ -52,16 +57,22 @@ export function ContextLimitBanner({ ...@@ -52,16 +57,22 @@ export function ContextLimitBanner({
<AlertTriangle className="h-3.5 w-3.5 shrink-0" /> <AlertTriangle className="h-3.5 w-3.5 shrink-0" />
<span>{message}</span> <span>{message}</span>
</span> </span>
<Tooltip>
<TooltipTrigger
render={
<Button <Button
onClick={handleSummarize} onClick={handleSummarize}
variant="outline" variant="outline"
size="sm" size="sm"
className="h-6 px-2 text-xs border-amber-500/40 bg-amber-500/5 text-amber-600 dark:text-amber-500 hover:bg-amber-500/20 hover:border-amber-500/60" className="h-6 px-2 text-xs border-amber-500/40 bg-amber-500/5 text-amber-600 dark:text-amber-500 hover:bg-amber-500/20 hover:border-amber-500/60"
title="Summarize to new chat" />
}
> >
Summarize Summarize
<ArrowRight className="h-3 w-3 ml-1" /> <ArrowRight className="h-3 w-3 ml-1" />
</Button> </TooltipTrigger>
<TooltipContent>Summarize to new chat</TooltipContent>
</Tooltip>
</div> </div>
); );
} }
import { SendIcon, StopCircleIcon } from "lucide-react"; import { SendIcon, StopCircleIcon } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { homeChatInputValueAtom } from "@/atoms/chatAtoms"; // Use a different atom for home input import { homeChatInputValueAtom } from "@/atoms/chatAtoms"; // Use a different atom for home input
...@@ -102,21 +107,37 @@ export function HomeChatInput({ ...@@ -102,21 +107,37 @@ export function HomeChatInput({
/> />
{isStreaming ? ( {isStreaming ? (
<Tooltip>
<TooltipTrigger
render={
<button <button
className="px-2 py-2 mt-1 mr-1 text-(--sidebar-accent-fg) rounded-lg opacity-50 cursor-not-allowed" // Indicate disabled state aria-label="Cancel generation (unavailable here)"
title="Cancel generation (unavailable here)" className="px-2 py-2 mt-1 mr-1 text-(--sidebar-accent-fg) rounded-lg opacity-50 cursor-not-allowed"
/>
}
> >
<StopCircleIcon size={20} /> <StopCircleIcon size={20} />
</button> </TooltipTrigger>
<TooltipContent>
Cancel generation (unavailable here)
</TooltipContent>
</Tooltip>
) : ( ) : (
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleCustomSubmit} onClick={handleCustomSubmit}
disabled={!inputValue.trim() && attachments.length === 0} disabled={!inputValue.trim() && attachments.length === 0}
aria-label="Send message"
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50" className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
title="Send message" />
}
> >
<SendIcon size={20} /> <SendIcon size={20} />
</button> </TooltipTrigger>
<TooltipContent>Send message</TooltipContent>
</Tooltip>
)} )}
</div> </div>
<div className="pl-2 pr-1 flex items-center justify-between pb-2"> <div className="pl-2 pr-1 flex items-center justify-between pb-2">
......
...@@ -5,6 +5,11 @@ import { ...@@ -5,6 +5,11 @@ import {
} from "@/atoms/previewAtoms"; } from "@/atoms/previewAtoms";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { Code2, X } from "lucide-react"; import { Code2, X } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
export function SelectedComponentsDisplay() { export function SelectedComponentsDisplay() {
const [selectedComponents, setSelectedComponents] = useAtom( const [selectedComponents, setSelectedComponents] = useAtom(
...@@ -57,13 +62,19 @@ export function SelectedComponentsDisplay() { ...@@ -57,13 +62,19 @@ export function SelectedComponentsDisplay() {
<span className="text-xs font-medium text-muted-foreground"> <span className="text-xs font-medium text-muted-foreground">
Selected Components ({selectedComponents.length}) Selected Components ({selectedComponents.length})
</span> </span>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleClearAll} onClick={handleClearAll}
className="text-xs text-muted-foreground hover:text-foreground transition-colors" className="text-xs text-muted-foreground hover:text-foreground transition-colors"
title="Clear all selected components" />
}
> >
Clear all Clear all
</button> </TooltipTrigger>
<TooltipContent>Clear all selected components</TooltipContent>
</Tooltip>
</div> </div>
{selectedComponents.map((selectedComponent, index) => ( {selectedComponents.map((selectedComponent, index) => (
<div key={selectedComponent.id} className="mb-1 last:mb-0"> <div key={selectedComponent.id} className="mb-1 last:mb-0">
...@@ -89,13 +100,20 @@ export function SelectedComponentsDisplay() { ...@@ -89,13 +100,20 @@ export function SelectedComponentsDisplay() {
</span> </span>
</div> </div>
</div> </div>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => handleRemoveComponent(index)} onClick={() => handleRemoveComponent(index)}
aria-label="Deselect component"
className="ml-2 flex-shrink-0 rounded-full p-0.5 hover:bg-indigo-600/20" className="ml-2 flex-shrink-0 rounded-full p-0.5 hover:bg-indigo-600/20"
title="Deselect component" />
}
> >
<X size={18} className="text-indigo-600 dark:text-indigo-400" /> <X size={18} className="text-indigo-600 dark:text-indigo-400" />
</button> </TooltipTrigger>
<TooltipContent>Deselect component</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
))} ))}
......
...@@ -24,6 +24,11 @@ import { ...@@ -24,6 +24,11 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { showError, showSuccess } from "@/lib/toast"; import { showError, showSuccess } from "@/lib/toast";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { useCheckProblems } from "@/hooks/useCheckProblems"; import { useCheckProblems } from "@/hooks/useCheckProblems";
...@@ -170,19 +175,25 @@ export const ActionHeader = () => { ...@@ -170,19 +175,25 @@ export const ActionHeader = () => {
badge?: React.ReactNode, badge?: React.ReactNode,
) => { ) => {
return ( return (
<Tooltip>
<TooltipTrigger
render={
<button <button
data-testid={testId} data-testid={testId}
ref={ref} ref={ref}
className="no-app-region-drag cursor-pointer relative flex items-center gap-0.5 px-2 py-0.5 rounded-md text-xs font-medium z-10 hover:bg-[var(--background)] flex-col" className="no-app-region-drag cursor-pointer relative flex items-center gap-0.5 px-2 py-0.5 rounded-md text-xs font-medium z-10 hover:bg-[var(--background)] flex-col"
onClick={() => selectPanel(mode)} onClick={() => selectPanel(mode)}
title={isCompact ? text : undefined} />
}
> >
{icon} {icon}
<span> <span>
{!isCompact && <span>{text}</span>} {!isCompact && <span>{text}</span>}
{badge} {badge}
</span> </span>
</button> </TooltipTrigger>
{isCompact && <TooltipContent>{text}</TooltipContent>}
</Tooltip>
); );
}; };
const iconSize = 15; const iconSize = 15;
...@@ -255,13 +266,19 @@ export const ActionHeader = () => { ...@@ -255,13 +266,19 @@ export const ActionHeader = () => {
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<ChatActivityButton /> <ChatActivityButton />
<DropdownMenu> <DropdownMenu>
<Tooltip>
<TooltipTrigger
render={
<DropdownMenuTrigger <DropdownMenuTrigger
data-testid="preview-more-options-button" data-testid="preview-more-options-button"
className="no-app-region-drag flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors" className="no-app-region-drag flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
title="More options" />
}
> >
<MoreVertical size={16} /> <MoreVertical size={16} />
</DropdownMenuTrigger> </TooltipTrigger>
<TooltipContent>More options</TooltipContent>
</Tooltip>
<DropdownMenuContent align="end" className="w-60"> <DropdownMenuContent align="end" className="w-60">
<DropdownMenuItem onClick={onCleanRestart}> <DropdownMenuItem onClick={onCleanRestart}>
<Cog size={16} /> <Cog size={16} />
......
...@@ -10,6 +10,11 @@ import { ...@@ -10,6 +10,11 @@ import {
} from "lucide-react"; } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ToolbarColorPicker } from "./ToolbarColorPicker"; import { ToolbarColorPicker } from "./ToolbarColorPicker";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface AnnotatorToolbarProps { interface AnnotatorToolbarProps {
tool: "select" | "draw" | "text"; tool: "select" | "draw" | "text";
...@@ -46,108 +51,158 @@ export const AnnotatorToolbar = ({ ...@@ -46,108 +51,158 @@ export const AnnotatorToolbar = ({
<div className="flex items-center justify-center p-2 border-b space-x-2"> <div className="flex items-center justify-center p-2 border-b space-x-2">
{/* Tool Selection Buttons */} {/* Tool Selection Buttons */}
<div className="flex space-x-1"> <div className="flex space-x-1">
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => onToolChange("select")} onClick={() => onToolChange("select")}
aria-label="Select" aria-label="Select"
title="Select"
className={cn( className={cn(
"p-1 rounded transition-colors duration-200", "p-1 rounded transition-colors duration-200",
tool === "select" tool === "select"
? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700" ? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700"
: " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900", : " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900",
)} )}
/>
}
> >
<MousePointer2 size={16} /> <MousePointer2 size={16} />
</button> </TooltipTrigger>
<TooltipContent>Select</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => onToolChange("draw")} onClick={() => onToolChange("draw")}
aria-label="Draw" aria-label="Draw"
title="Draw"
className={cn( className={cn(
"p-1 rounded transition-colors duration-200", "p-1 rounded transition-colors duration-200",
tool === "draw" tool === "draw"
? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700" ? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700"
: " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900", : " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900",
)} )}
/>
}
> >
<Pencil size={16} /> <Pencil size={16} />
</button> </TooltipTrigger>
<TooltipContent>Draw</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => onToolChange("text")} onClick={() => onToolChange("text")}
aria-label="Text" aria-label="Text"
title="Text"
className={cn( className={cn(
"p-1 rounded transition-colors duration-200", "p-1 rounded transition-colors duration-200",
tool === "text" tool === "text"
? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700" ? "bg-purple-500 text-white hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700"
: "text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900", : "text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900",
)} )}
/>
}
> >
<Type size={16} /> <Type size={16} />
</button> </TooltipTrigger>
<TooltipContent>Text</TooltipContent>
</Tooltip>
<div <Tooltip>
className="p-1 rounded transition-colors duration-200 hover:bg-purple-200 dark:hover:bg-purple-900" <TooltipTrigger>
title="Color" <div className="p-1 rounded transition-colors duration-200 hover:bg-purple-200 dark:hover:bg-purple-900">
>
<ToolbarColorPicker color={color} onChange={onColorChange} /> <ToolbarColorPicker color={color} onChange={onColorChange} />
</div> </div>
</TooltipTrigger>
<TooltipContent>Color</TooltipContent>
</Tooltip>
<div className="w-px bg-gray-200 dark:bg-gray-700 h-4" /> <div className="w-px bg-gray-200 dark:bg-gray-700 h-4" />
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onDelete} onClick={onDelete}
aria-label="Delete" aria-label="Delete"
title="Delete Selected"
className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed" className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!selectedId} disabled={!selectedId}
/>
}
> >
<Trash2 size={16} /> <Trash2 size={16} />
</button> </TooltipTrigger>
<TooltipContent>Delete Selected</TooltipContent>
</Tooltip>
<div className="w-px bg-gray-200 dark:bg-gray-700 h-4" /> <div className="w-px bg-gray-200 dark:bg-gray-700 h-4" />
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onUndo} onClick={onUndo}
aria-label="Undo" aria-label="Undo"
title="Undo"
className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed" className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={historyStep === 0} disabled={historyStep === 0}
/>
}
> >
<Undo size={16} /> <Undo size={16} />
</button> </TooltipTrigger>
<TooltipContent>Undo</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onRedo} onClick={onRedo}
aria-label="Redo" aria-label="Redo"
title="Redo"
className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed" className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={historyStep === historyLength - 1} disabled={historyStep === historyLength - 1}
/>
}
> >
<Redo size={16} /> <Redo size={16} />
</button> </TooltipTrigger>
<TooltipContent>Redo</TooltipContent>
</Tooltip>
<div className="w-px bg-gray-200 dark:bg-gray-700 h-4" /> <div className="w-px bg-gray-200 dark:bg-gray-700 h-4" />
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onSubmit} onClick={onSubmit}
aria-label="Add to Chat" aria-label="Add to Chat"
title="Add to Chat"
className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed" className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={!hasSubmitHandler} disabled={!hasSubmitHandler}
/>
}
> >
<Check size={16} /> <Check size={16} />
</button> </TooltipTrigger>
<TooltipContent>Add to Chat</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onDeactivate} onClick={onDeactivate}
aria-label="Close Annotator" aria-label="Close Annotator"
title="Close Annotator"
className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900" className="p-1 rounded transition-colors duration-200 text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900"
/>
}
> >
<X size={16} /> <X size={16} />
</button> </TooltipTrigger>
<TooltipContent>Close Annotator</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
); );
......
...@@ -3,6 +3,11 @@ import { FileTree } from "./FileTree"; ...@@ -3,6 +3,11 @@ import { FileTree } from "./FileTree";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLoadApp } from "@/hooks/useLoadApp"; import { useLoadApp } from "@/hooks/useLoadApp";
import { RefreshCw, Maximize2, Minimize2 } from "lucide-react"; import { RefreshCw, Maximize2, Minimize2 } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { selectedFileAtom } from "@/atoms/viewAtoms"; import { selectedFileAtom } from "@/atoms/viewAtoms";
...@@ -59,23 +64,37 @@ export const CodeView = ({ loading, app }: CodeViewProps) => { ...@@ -59,23 +64,37 @@ export const CodeView = ({ loading, app }: CodeViewProps) => {
> >
{/* Toolbar */} {/* Toolbar */}
<div className="flex items-center p-2 border-b space-x-2"> <div className="flex items-center p-2 border-b space-x-2">
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => refreshApp()} onClick={() => refreshApp()}
className="p-1 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed" className="p-1 rounded hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={loading || !app.id} disabled={loading || !app.id}
title="Refresh Files" />
}
> >
<RefreshCw size={16} /> <RefreshCw size={16} />
</button> </TooltipTrigger>
<TooltipContent>Refresh Files</TooltipContent>
</Tooltip>
<div className="text-sm text-gray-500">{app.files.length} files</div> <div className="text-sm text-gray-500">{app.files.length} files</div>
<div className="flex-1" /> <div className="flex-1" />
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => setIsFullscreen((value) => !value)} onClick={() => setIsFullscreen((value) => !value)}
className="p-1 rounded hover:bg-gray-200" className="p-1 rounded hover:bg-gray-200"
title={isFullscreen ? "Exit full screen" : "Enter full screen"} />
}
> >
{isFullscreen ? <Minimize2 size={16} /> : <Maximize2 size={16} />} {isFullscreen ? <Minimize2 size={16} /> : <Maximize2 size={16} />}
</button> </TooltipTrigger>
<TooltipContent>
{isFullscreen ? "Exit full screen" : "Enter full screen"}
</TooltipContent>
</Tooltip>
</div> </div>
{/* Content */} {/* Content */}
......
...@@ -7,6 +7,11 @@ import { ...@@ -7,6 +7,11 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { chatInputValueAtom } from "@/atoms/chatAtoms"; import { chatInputValueAtom } from "@/atoms/chatAtoms";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface ConsoleEntryProps { interface ConsoleEntryProps {
type: "server" | "client" | "edge-function" | "network-requests"; type: "server" | "client" | "edge-function" | "network-requests";
...@@ -120,14 +125,20 @@ export const ConsoleEntryComponent = (props: ConsoleEntryProps) => { ...@@ -120,14 +125,20 @@ export const ConsoleEntryComponent = (props: ConsoleEntryProps) => {
)} )}
</span> </span>
</div> </div>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleSendToChat} onClick={handleSendToChat}
title="Send to chat"
className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded" className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded"
data-testid="send-to-chat" data-testid="send-to-chat"
/>
}
> >
<MessageSquare size={12} className="text-gray-500" /> <MessageSquare size={12} className="text-gray-500" />
</button> </TooltipTrigger>
<TooltipContent>Send to chat</TooltipContent>
</Tooltip>
</div> </div>
); );
}; };
import { Filter, X, Trash2 } from "lucide-react"; import { Filter, X, Trash2 } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface ConsoleFiltersProps { interface ConsoleFiltersProps {
levelFilter: "all" | "info" | "warn" | "error"; levelFilter: "all" | "info" | "warn" | "error";
...@@ -109,14 +114,20 @@ export const ConsoleFilters = ({ ...@@ -109,14 +114,20 @@ export const ConsoleFilters = ({
)} )}
{/* Clear logs button */} {/* Clear logs button */}
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onClearLogs} onClick={onClearLogs}
className="p-1 border border-border rounded bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" className="p-1 border border-border rounded bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
data-testid="clear-logs-button" data-testid="clear-logs-button"
title="Clear logs" />
}
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </TooltipTrigger>
<TooltipContent>Clear logs</TooltipContent>
</Tooltip>
<div className="ml-auto text-xs text-gray-500">{totalLogs} logs</div> <div className="ml-auto text-xs text-gray-500">{totalLogs} logs</div>
</div> </div>
......
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { X } from "lucide-react"; import { X } from "lucide-react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface DraggableTextInputProps { interface DraggableTextInputProps {
input: { input: {
...@@ -158,6 +163,9 @@ export const DraggableTextInput = ({ ...@@ -158,6 +163,9 @@ export const DraggableTextInput = ({
/> />
{/* Close Button - Rightmost */} {/* Close Button - Rightmost */}
<Tooltip>
<TooltipTrigger
render={
<button <button
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors z-10 group" className="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors z-10 group"
onClick={(e) => { onClick={(e) => {
...@@ -165,11 +173,14 @@ export const DraggableTextInput = ({ ...@@ -165,11 +173,14 @@ export const DraggableTextInput = ({
e.stopPropagation(); e.stopPropagation();
onRemove(input.id); onRemove(input.id);
}} }}
title="Remove text input"
type="button" type="button"
/>
}
> >
<X className="w-3 h-3 text-gray-400 dark:text-gray-500 group-hover:text-red-600 dark:group-hover:text-red-400" /> <X className="w-3 h-3 text-gray-400 dark:text-gray-500 group-hover:text-red-600 dark:group-hover:text-red-400" />
</button> </TooltipTrigger>
<TooltipContent>Remove text input</TooltipContent>
</Tooltip>
</div> </div>
</div> </div>
); );
......
...@@ -12,6 +12,11 @@ import { useSettings } from "@/hooks/useSettings"; ...@@ -12,6 +12,11 @@ import { useSettings } from "@/hooks/useSettings";
import { useCheckProblems } from "@/hooks/useCheckProblems"; import { useCheckProblems } from "@/hooks/useCheckProblems";
import { getLanguage } from "@/utils/get_language"; import { getLanguage } from "@/utils/get_language";
import { queryKeys } from "@/lib/queryKeys"; import { queryKeys } from "@/lib/queryKeys";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
interface FileEditorProps { interface FileEditorProps {
appId: number | null; appId: number | null;
...@@ -53,6 +58,9 @@ const Breadcrumb: React.FC<BreadcrumbProps> = ({ ...@@ -53,6 +58,9 @@ const Breadcrumb: React.FC<BreadcrumbProps> = ({
))} ))}
</div> </div>
<div className="flex items-center gap-2 flex-shrink-0 ml-2"> <div className="flex items-center gap-2 flex-shrink-0 ml-2">
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
...@@ -60,10 +68,15 @@ const Breadcrumb: React.FC<BreadcrumbProps> = ({ ...@@ -60,10 +68,15 @@ const Breadcrumb: React.FC<BreadcrumbProps> = ({
disabled={!hasUnsavedChanges || isSaving} disabled={!hasUnsavedChanges || isSaving}
className="h-6 w-6 p-0" className="h-6 w-6 p-0"
data-testid="save-file-button" data-testid="save-file-button"
title={hasUnsavedChanges ? "Save changes" : "No unsaved changes"} />
}
> >
<Save size={12} /> <Save size={12} />
</Button> </TooltipTrigger>
<TooltipContent>
{hasUnsavedChanges ? "Save changes" : "No unsaved changes"}
</TooltipContent>
</Tooltip>
{hasUnsavedChanges && ( {hasUnsavedChanges && (
<Circle <Circle
size={8} size={8}
......
...@@ -57,6 +57,11 @@ import { ...@@ -57,6 +57,11 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { useRunApp } from "@/hooks/useRunApp"; import { useRunApp } from "@/hooks/useRunApp";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useShortcut } from "@/hooks/useShortcut"; import { useShortcut } from "@/hooks/useShortcut";
...@@ -989,18 +994,29 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -989,18 +994,29 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
<div className="flex items-center p-2 border-b space-x-2"> <div className="flex items-center p-2 border-b space-x-2">
{/* Navigation Buttons */} {/* Navigation Buttons */}
<div className="flex space-x-1"> <div className="flex space-x-1">
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={() => setIsChatPanelHidden(!isChatPanelHidden)} onClick={() => setIsChatPanelHidden(!isChatPanelHidden)}
className="p-1 rounded transition-colors duration-200 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300" className="p-1 rounded transition-colors duration-200 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
data-testid="preview-toggle-chat-panel-button" data-testid="preview-toggle-chat-panel-button"
title={isChatPanelHidden ? "Show chat" : "Hide chat"} />
}
> >
{isChatPanelHidden ? ( {isChatPanelHidden ? (
<Maximize2 size={16} /> <Maximize2 size={16} />
) : ( ) : (
<Minimize2 size={16} /> <Minimize2 size={16} />
)} )}
</button> </TooltipTrigger>
<TooltipContent>
{isChatPanelHidden ? "Show chat" : "Hide chat"}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleActivateComponentSelector} onClick={handleActivateComponentSelector}
className={`p-1 rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed ${ className={`p-1 rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed ${
...@@ -1009,17 +1025,25 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1009,17 +1025,25 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
: " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900" : " text-purple-700 hover:bg-purple-200 dark:text-purple-300 dark:hover:bg-purple-900"
}`} }`}
disabled={ disabled={
loading || !selectedAppId || !isComponentSelectorInitialized loading ||
!selectedAppId ||
!isComponentSelectorInitialized
} }
data-testid="preview-pick-element-button" data-testid="preview-pick-element-button"
title={ />
isPicking
? "Deactivate component selector"
: `Select component (${isMac ? "⌘ + ⇧ + C" : "Ctrl + ⇧ + C"})`
} }
> >
<MousePointerClick size={16} /> <MousePointerClick size={16} />
</button> </TooltipTrigger>
<TooltipContent>
{isPicking
? "Deactivate component selector"
: `Select component (${isMac ? "⌘ + ⇧ + C" : "Ctrl + ⇧ + C"})`}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleAnnotatorClick} onClick={handleAnnotatorClick}
className={`p-1 rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed ${ className={`p-1 rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed ${
...@@ -1034,12 +1058,15 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1034,12 +1058,15 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
!isComponentSelectorInitialized !isComponentSelectorInitialized
} }
data-testid="preview-annotator-button" data-testid="preview-annotator-button"
title={ />
annotatorMode ? "Annotator mode active" : "Activate annotator"
} }
> >
<Pen size={16} /> <Pen size={16} />
</button> </TooltipTrigger>
<TooltipContent>
{annotatorMode ? "Annotator mode active" : "Activate annotator"}
</TooltipContent>
</Tooltip>
<button <button
className="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed dark:text-gray-300" className="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed dark:text-gray-300"
disabled={!canGoBack || loading || !selectedAppId} disabled={!canGoBack || loading || !selectedAppId}
...@@ -1106,14 +1133,20 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1106,14 +1133,20 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex space-x-1"> <div className="flex space-x-1">
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={onRestart} onClick={onRestart}
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors" className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
title="Restart App" />
}
> >
<Power size={16} /> <Power size={16} />
<span>Restart</span> <span>Restart</span>
</button> </TooltipTrigger>
<TooltipContent>Restart App</TooltipContent>
</Tooltip>
<button <button
data-testid="preview-open-browser-button" data-testid="preview-open-browser-button"
onClick={() => { onClick={() => {
...@@ -1128,6 +1161,9 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1128,6 +1161,9 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
{/* Device Mode Button */} {/* Device Mode Button */}
<Popover open={isDevicePopoverOpen} modal={false}> <Popover open={isDevicePopoverOpen} modal={false}>
<Tooltip>
<TooltipTrigger
render={
<PopoverTrigger <PopoverTrigger
data-testid="device-mode-button" data-testid="device-mode-button"
onClick={() => { onClick={() => {
...@@ -1138,12 +1174,16 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1138,12 +1174,16 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
}} }}
className={cn( className={cn(
"p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 dark:text-gray-300", "p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 dark:text-gray-300",
deviceMode !== "desktop" && "bg-gray-200 dark:bg-gray-700", deviceMode !== "desktop" &&
"bg-gray-200 dark:bg-gray-700",
)} )}
title="Device Mode" />
}
> >
<MonitorSmartphone size={16} /> <MonitorSmartphone size={16} />
</PopoverTrigger> </TooltipTrigger>
<TooltipContent>Device Mode</TooltipContent>
</Tooltip>
<PopoverContent className="w-auto p-2"> <PopoverContent className="w-auto p-2">
<ToggleGroup <ToggleGroup
value={[deviceMode]} value={[deviceMode]}
...@@ -1159,27 +1199,45 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { ...@@ -1159,27 +1199,45 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
}} }}
variant="outline" variant="outline"
> >
<Tooltip>
<TooltipTrigger
render={
<ToggleGroupItem <ToggleGroupItem
value="desktop" value="desktop"
aria-label="Desktop view" aria-label="Desktop view"
title="Desktop" />
}
> >
<Monitor size={16} /> <Monitor size={16} />
</ToggleGroupItem> </TooltipTrigger>
<TooltipContent>Desktop</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<ToggleGroupItem <ToggleGroupItem
value="tablet" value="tablet"
aria-label="Tablet view" aria-label="Tablet view"
title="Tablet" />
}
> >
<Tablet size={16} className="scale-x-130" /> <Tablet size={16} className="scale-x-130" />
</ToggleGroupItem> </TooltipTrigger>
<TooltipContent>Tablet</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<ToggleGroupItem <ToggleGroupItem
value="mobile" value="mobile"
aria-label="Mobile view" aria-label="Mobile view"
title="Mobile" />
}
> >
<Smartphone size={16} /> <Smartphone size={16} />
</ToggleGroupItem> </TooltipTrigger>
<TooltipContent>Mobile</TooltipContent>
</Tooltip>
</ToggleGroup> </ToggleGroup>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { X, Move, Square, Palette, Type } from "lucide-react"; import { X, Move, Square, Palette, Type } from "lucide-react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { ComponentSelection } from "@/ipc/types"; import { ComponentSelection } from "@/ipc/types";
...@@ -314,14 +319,20 @@ export function VisualEditingToolbar({ ...@@ -314,14 +319,20 @@ export function VisualEditingToolbar({
left: `${toolbarLeft}px`, left: `${toolbarLeft}px`,
}} }}
> >
<Tooltip>
<TooltipTrigger
render={
<button <button
onClick={handleDeselectComponent} onClick={handleDeselectComponent}
className="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-[#7f22fe] dark:text-gray-200" className="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-[#7f22fe] dark:text-gray-200"
aria-label="Deselect Component" aria-label="Deselect Component"
title="Deselect Component" />
}
> >
<X size={16} /> <X size={16} />
</button> </TooltipTrigger>
<TooltipContent>Deselect Component</TooltipContent>
</Tooltip>
{isDynamic ? ( {isDynamic ? (
<div className="flex items-center px-2 py-1 text-yellow-800 dark:text-yellow-200 rounded text-xs font-medium"> <div className="flex items-center px-2 py-1 text-yellow-800 dark:text-yellow-200 rounded text-xs font-medium">
......
...@@ -513,7 +513,7 @@ function SidebarMenuButton<T extends React.ElementType = "button">({ ...@@ -513,7 +513,7 @@ function SidebarMenuButton<T extends React.ElementType = "button">({
return ( return (
<Tooltip> <Tooltip>
<TooltipTrigger>{button}</TooltipTrigger> <TooltipTrigger render={button} />
<TooltipContent <TooltipContent
side="right" side="right"
align="center" align="center"
......
...@@ -18,11 +18,7 @@ function TooltipProvider({ ...@@ -18,11 +18,7 @@ function TooltipProvider({
} }
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
return ( return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
);
} }
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
...@@ -32,7 +28,7 @@ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) { ...@@ -32,7 +28,7 @@ function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
function TooltipContent({ function TooltipContent({
className, className,
side = "top", side = "top",
sideOffset = 0, sideOffset = 4,
align = "center", align = "center",
alignOffset = 0, alignOffset = 0,
children, children,
...@@ -54,13 +50,13 @@ function TooltipContent({ ...@@ -54,13 +50,13 @@ function TooltipContent({
<TooltipPrimitive.Popup <TooltipPrimitive.Popup
data-slot="tooltip-content" data-slot="tooltip-content"
className={cn( className={cn(
"bg-primary text-primary-foreground data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance origin-(--transform-origin)", "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> <TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup> </TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner> </TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
......
...@@ -18,6 +18,11 @@ import { ...@@ -18,6 +18,11 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Dialog, Dialog,
...@@ -357,6 +362,9 @@ export default function AppDetailsPage() { ...@@ -357,6 +362,9 @@ export default function AppDetailsPage() {
Path Path
</span> </span>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger
render={
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
...@@ -364,10 +372,13 @@ export default function AppDetailsPage() { ...@@ -364,10 +372,13 @@ export default function AppDetailsPage() {
onClick={() => { onClick={() => {
ipc.system.showItemInFolder(currentAppPath); ipc.system.showItemInFolder(currentAppPath);
}} }}
title="Show in folder" />
}
> >
<Folder className="h-3.5 w-3.5" /> <Folder className="h-3.5 w-3.5" />
</Button> </TooltipTrigger>
<TooltipContent>Show in folder</TooltipContent>
</Tooltip>
<span className="text-sm break-all">{currentAppPath}</span> <span className="text-sm break-all">{currentAppPath}</span>
</div> </div>
</div> </div>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论