feat(editor): Items category bar as icon-grid with tooltips (#349)
Render the function-tree category roots as a Build-style square-tile grid (icon when available, otherwise a two-letter abbreviation) with a hover tooltip for the full name, so the Items and Build panels read the same. Share one TooltipProvider with disableHoverableContent + the icon-grid tooltip class to avoid the flicker when moving between adjacent tiles. Legacy fallback panel category bar wraps instead of scrolling horizontally. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
986d75026f
commit
f51cea9a85
+25
-10
@@ -1,10 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { AssetInput } from '@pascal-app/core'
|
import type { AssetInput } from '@pascal-app/core'
|
||||||
|
import { Root as TooltipRoot } from '@radix-ui/react-tooltip'
|
||||||
import NextImage from 'next/image'
|
import NextImage from 'next/image'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { cn } from '../../../../../lib/utils'
|
import { cn } from '../../../../../lib/utils'
|
||||||
import { ItemCatalog } from '../../../item-catalog/item-catalog'
|
import { ItemCatalog } from '../../../item-catalog/item-catalog'
|
||||||
|
import {
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '../../../../../components/ui/primitives/tooltip'
|
||||||
|
|
||||||
/** A function-axis taxonomy node, assembled into a tree by the embedder. */
|
/** A function-axis taxonomy node, assembled into a tree by the embedder. */
|
||||||
export type FunctionTreeNode = {
|
export type FunctionTreeNode = {
|
||||||
@@ -109,40 +115,49 @@ export function FunctionTreePanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Root nodes as category tabs */}
|
{/* Root nodes as a category grid — icon when available, otherwise a
|
||||||
<div className="flex shrink-0 gap-1 overflow-x-auto border-border/70 border-b p-2">
|
two-letter abbreviation, with the full name in a hover tooltip.
|
||||||
|
Mirrors the Build tab's tile grid so the two panels read the same. */}
|
||||||
|
<TooltipProvider delayDuration={0} disableHoverableContent>
|
||||||
|
<div className="grid shrink-0 grid-cols-5 gap-1.5 border-border/70 border-b p-2">
|
||||||
{functionTree.map((root) => {
|
{functionTree.map((root) => {
|
||||||
const isActive = activeRoot?.slug === root.slug
|
const isActive = activeRoot?.slug === root.slug
|
||||||
return (
|
return (
|
||||||
|
<TooltipRoot key={root.slug}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex shrink-0 flex-col items-center gap-1 rounded-xl px-3 py-2 transition-colors',
|
'relative flex aspect-square items-center justify-center rounded-xl transition-all duration-200',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
? 'bg-primary/10 ring-1 ring-primary/50'
|
||||||
: 'text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground',
|
: 'bg-muted/40 opacity-70 grayscale hover:bg-muted hover:opacity-100 hover:grayscale-0',
|
||||||
)}
|
)}
|
||||||
key={root.slug}
|
|
||||||
onClick={() => selectRoot(root.slug)}
|
onClick={() => selectRoot(root.slug)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{root.iconUrl ? (
|
{root.iconUrl ? (
|
||||||
<NextImage
|
<NextImage
|
||||||
alt={root.name}
|
alt={root.name}
|
||||||
className={cn('size-7 object-contain', !isActive && 'opacity-60 grayscale')}
|
className="size-7 object-contain"
|
||||||
height={28}
|
height={28}
|
||||||
src={root.iconUrl}
|
src={root.iconUrl}
|
||||||
width={28}
|
width={28}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex size-7 items-center justify-center rounded-lg bg-muted/50 font-semibold text-[11px] uppercase">
|
<span className="font-semibold text-muted-foreground text-xs uppercase">
|
||||||
{root.name.slice(0, 2)}
|
{root.name.slice(0, 2)}
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="font-medium text-[10px] leading-none">{root.name}</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="icon-grid-tooltip pointer-events-none" side="top">
|
||||||
|
{root.name}
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipRoot>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
{/* Search + source filter */}
|
{/* Search + source filter */}
|
||||||
<div className="flex shrink-0 flex-col gap-2 border-border/70 border-b p-2">
|
<div className="flex shrink-0 flex-col gap-2 border-border/70 border-b p-2">
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ function LegacyItemsPanel({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Category tabs */}
|
{/* Category tabs */}
|
||||||
<div className="flex shrink-0 gap-1 overflow-x-auto border-border/70 border-b p-2">
|
<div className="flex shrink-0 flex-wrap gap-1 border-border/70 border-b p-2">
|
||||||
{furnishTools.map((cat) => {
|
{furnishTools.map((cat) => {
|
||||||
const isActive = activeCategory.catalogCategory === cat.catalogCategory
|
const isActive = activeCategory.catalogCategory === cat.catalogCategory
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user