mirror of
https://github.com/FranP-code/classify_saved_videos_yt.git
synced 2025-10-13 00:32:25 +00:00
Update tooltip implementation and add TooltipContent and TooltipTrigger components
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.0",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@base-ui-components/react": "1.0.0-beta.1",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
|
||||
84
web/pnpm-lock.yaml
generated
84
web/pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
||||
'@astrojs/react':
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(@types/node@24.0.13)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@base-ui-components/react':
|
||||
specifier: 1.0.0-beta.1
|
||||
version: 1.0.0-beta.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
|
||||
@@ -182,6 +185,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.27.6':
|
||||
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -194,6 +201,17 @@ packages:
|
||||
resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@base-ui-components/react@1.0.0-beta.1':
|
||||
resolution: {integrity: sha512-7zmGiz4/+HKnv99lWftItoSMqnj2PdSvt2krh0/GP+Rj0xK0NMnFI/gIVvP7CB2G+k0JPUrRWXjXa3y08oiakg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17 || ^18 || ^19
|
||||
react: ^17 || ^18 || ^19
|
||||
react-dom: ^17 || ^18 || ^19
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@capsizecss/unpack@2.4.0':
|
||||
resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==}
|
||||
|
||||
@@ -356,6 +374,21 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@floating-ui/core@1.7.2':
|
||||
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.4':
|
||||
resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
@@ -1729,6 +1762,9 @@ packages:
|
||||
remark-stringify@11.0.0:
|
||||
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
restructure@3.0.2:
|
||||
resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==}
|
||||
|
||||
@@ -1814,6 +1850,9 @@ packages:
|
||||
style-to-object@1.0.9:
|
||||
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@3.3.1:
|
||||
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
|
||||
|
||||
@@ -1991,6 +2030,11 @@ packages:
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
use-sync-external-store@1.5.0:
|
||||
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
vfile-location@5.0.3:
|
||||
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
||||
|
||||
@@ -2296,6 +2340,8 @@ snapshots:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/runtime@7.27.6': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
@@ -2319,6 +2365,19 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@base-ui-components/react@1.0.0-beta.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@floating-ui/utils': 0.2.10
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
reselect: 5.1.1
|
||||
tabbable: 6.2.0
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
'@capsizecss/unpack@2.4.0':
|
||||
dependencies:
|
||||
blob-to-buffer: 1.2.9
|
||||
@@ -2410,6 +2469,23 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.25.6':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.2':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.2
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.2
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.0.4
|
||||
@@ -4190,6 +4266,8 @@ snapshots:
|
||||
mdast-util-to-markdown: 2.1.2
|
||||
unified: 11.0.5
|
||||
|
||||
reselect@5.1.1: {}
|
||||
|
||||
restructure@3.0.2: {}
|
||||
|
||||
retext-latin@4.0.0:
|
||||
@@ -4335,6 +4413,8 @@ snapshots:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@3.3.1: {}
|
||||
|
||||
tailwindcss@4.1.11: {}
|
||||
@@ -4474,6 +4554,10 @@ snapshots:
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
use-sync-external-store@1.5.0(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
vfile-location@5.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { VideoData } from '../types/video';
|
||||
import { formatDuration, formatDate } from '../utils/csvParser';
|
||||
import { Badge } from './ui/badge';
|
||||
import { Button } from './ui/button';
|
||||
import { Tooltip } from './ui/tooltip';
|
||||
import { Tooltip,TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||
|
||||
interface VirtualTableProps {
|
||||
data: VideoData[];
|
||||
@@ -74,24 +74,32 @@ export function VirtualTable({ data }: VirtualTableProps) {
|
||||
<div className="grid grid-cols-12 gap-3 px-4 py-3 items-center min-h-[80px]">
|
||||
{/* Video Info */}
|
||||
<div className="col-span-3 space-y-2 min-w-0">
|
||||
<Tooltip content={video.video_title}>
|
||||
<h3 className="font-medium text-sm leading-tight truncate">
|
||||
<Tooltip>
|
||||
<TooltipContent>
|
||||
{video.video_title}
|
||||
</TooltipContent>
|
||||
<TooltipTrigger>
|
||||
<h3 className="text-start font-medium text-sm leading-tight truncate sm:w-[150px] lg:w-[200px] xl:w-[250px] 2xl:w-[300px]">
|
||||
{video.video_title}
|
||||
</h3>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{video.detailed_subtags.split(',').slice(0, 2).map((tag, i) => (
|
||||
<Tooltip key={i} content={tag.trim()}>
|
||||
<Badge variant="secondary" className="text-xs max-w-20 truncate">
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
))}
|
||||
{video.detailed_subtags.split(',').length > 2 && (
|
||||
<Tooltip content={`+${video.detailed_subtags.split(',').length - 2} more tags`}>
|
||||
<Tooltip>
|
||||
<TooltipContent>
|
||||
{video.detailed_subtags.split(',').slice(2).join(', ')}
|
||||
</TooltipContent>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{video.detailed_subtags.split(',').length - 2}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,36 +1,103 @@
|
||||
import * as React from "react"
|
||||
import { Tooltip as BaseTooltip } from "@base-ui-components/react/tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface TooltipProps {
|
||||
content: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
function TooltipProvider({
|
||||
delay = 0,
|
||||
closeDelay = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Provider>) {
|
||||
return (
|
||||
<BaseTooltip.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delay={delay}
|
||||
closeDelay={closeDelay}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Tooltip({ content, children, className }: TooltipProps) {
|
||||
const [isVisible, setIsVisible] = React.useState(false)
|
||||
function Tooltip({ ...props }: React.ComponentProps<typeof BaseTooltip.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<BaseTooltip.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative inline-block">
|
||||
<div
|
||||
onMouseEnter={() => setIsVisible(true)}
|
||||
onMouseLeave={() => setIsVisible(false)}
|
||||
className="cursor-help"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{isVisible && (
|
||||
<div className={cn(
|
||||
"absolute z-50 px-3 py-2 text-sm text-white bg-gray-900 dark:bg-gray-700 rounded-lg shadow-lg",
|
||||
"bottom-full left-1/2 transform -translate-x-1/2 mb-2",
|
||||
"max-w-xs break-words",
|
||||
"before:content-[''] before:absolute before:top-full before:left-1/2 before:transform before:-translate-x-1/2",
|
||||
"before:border-4 before:border-transparent before:border-t-gray-900 dark:before:border-t-gray-700",
|
||||
className
|
||||
)}>
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Trigger>) {
|
||||
return <BaseTooltip.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
function TooltipPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Portal>) {
|
||||
return <BaseTooltip.Portal data-slot="tooltip-portal" {...props} />
|
||||
}
|
||||
|
||||
function TooltipPositioner({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Positioner>) {
|
||||
return <BaseTooltip.Positioner data-slot="tooltip-positioner" {...props} />
|
||||
}
|
||||
|
||||
function TooltipArrow({
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Arrow>) {
|
||||
return <BaseTooltip.Arrow data-slot="tooltip-arrow" {...props} />
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 8,
|
||||
side = "top",
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof BaseTooltip.Popup> & {
|
||||
align?: BaseTooltip.Positioner.Props["align"]
|
||||
side?: BaseTooltip.Positioner.Props["side"]
|
||||
sideOffset?: BaseTooltip.Positioner.Props["sideOffset"]
|
||||
}) {
|
||||
return (
|
||||
<TooltipPortal>
|
||||
<TooltipPositioner sideOffset={sideOffset} align={align} side={side}>
|
||||
<BaseTooltip.Popup
|
||||
data-slot="tooltip-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground outline-border z-50 w-fit origin-[var(--transform-origin)] rounded-md px-3 py-1.5 text-xs text-balance shadow-sm outline -outline-offset-1 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipArrow className="data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180">
|
||||
<svg width="20" height="10" viewBox="0 0 20 10" fill="none">
|
||||
<path
|
||||
d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V9H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
|
||||
className="fill-popover"
|
||||
/>
|
||||
<path
|
||||
d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
|
||||
className="fill-border"
|
||||
/>
|
||||
</svg>
|
||||
</TooltipArrow>
|
||||
</BaseTooltip.Popup>
|
||||
</TooltipPositioner>
|
||||
</TooltipPortal>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipPortal,
|
||||
TooltipPositioner,
|
||||
TooltipArrow,
|
||||
TooltipProvider,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user