diff --git a/web/src/app/app/settings/inbox-tagging/page.tsx b/web/src/app/app/settings/inbox-tagging/page.tsx index 639501721..237a86c98 100644 --- a/web/src/app/app/settings/inbox-tagging/page.tsx +++ b/web/src/app/app/settings/inbox-tagging/page.tsx @@ -25,11 +25,11 @@ import { StatStrip, } from "@/components/layout/Page"; import { NoAccess } from "@/components/layout/NoAccess"; +import { TagMeaningTooltip } from "@/components/ui/tag-meaning-tooltip"; import { usePermission } from "@/hooks/usePermission"; import useInboxTagReview from "@/lib/api/hooks/app/inboxtag/useInboxTagReview"; import type { InboxTagRow } from "@/lib/api/models/app/inboxtag/InboxTagReview"; import { cn } from "@/lib/utils"; -import { tagMeaning } from "@/lib/unibox/tagMeanings"; const PRIORITY_TONE: Record = { now: "bg-rose-50 text-rose-700", @@ -83,13 +83,11 @@ function Row({ r }: { r: InboxTagRow }) { no labels ) : ( r.labels.map((l) => ( - - {l} - + + + {l} + + )) )} diff --git a/web/src/components/app/contacts/CategoryPicker.tsx b/web/src/components/app/contacts/CategoryPicker.tsx index 8db605530..aa4fef97a 100644 --- a/web/src/components/app/contacts/CategoryPicker.tsx +++ b/web/src/components/app/contacts/CategoryPicker.tsx @@ -25,7 +25,7 @@ import useFlipPlacement from "@/hooks/useFlipPlacement"; import clippedTitle from "@/lib/helper/clippedTitle"; import useCreateCategory from "@/lib/api/hooks/app/categories/useCreateCategory"; import type Category from "@/lib/api/models/app/Category"; -import { tagMeaning } from "@/lib/unibox/tagMeanings"; +import { TagMeaningTooltip } from "@/components/ui/tag-meaning-tooltip"; interface Props { // Selected ids — kept as ids so the consumer can store them in the @@ -225,46 +225,41 @@ export function CategoryChip({ onRemove?: () => void; compact?: boolean; }) { - // Automatic labels carry their meaning on hover. A chip reading "going-cold" - // is only useful to somebody who already knows the taxonomy, and nobody - // does on the first day. A label the workspace made itself needs no gloss, - // so it gets the plain title. - const meaning = tagMeaning(category.title); - return ( - + - - {category.title} - - {onRemove && ( - - )} - + {category.title} + + {onRemove && ( + + )} + + ); } diff --git a/web/src/components/app/unibox/ScopeRail.tsx b/web/src/components/app/unibox/ScopeRail.tsx index c8684c839..de510e321 100644 --- a/web/src/components/app/unibox/ScopeRail.tsx +++ b/web/src/components/app/unibox/ScopeRail.tsx @@ -47,7 +47,8 @@ import { PopoverMenuTrigger, } from "@/components/ui/popover-menu"; import type { UniboxFolder } from "@/lib/api/models/app/unibox/UniboxSearch"; -import { tagMeaning } from "@/lib/unibox/tagMeanings"; +import { TagMeaningTooltip } from "@/components/ui/tag-meaning-tooltip"; +import { isAutomaticTag } from "@/lib/unibox/tagMeanings"; export type UniboxScope = | { kind: "all" } @@ -246,16 +247,17 @@ export function ScopeRail({ scope, onChange }: ScopeRailProps) { searchPlaceholder="Filter labels" getSearchKey={(c) => c.title} renderItem={(c) => ( - } - label={c.title} - title={tagMeaning(c.title) || undefined} - count={c.unread || c.total || undefined} - accent={c.unread > 0} - active={active === `category:${c.id}`} - onClick={() => onChange({ kind: "category", categoryId: c.id })} - /> + + } + label={c.title} + hideNativeTitle={isAutomaticTag(c.title)} + count={c.unread || c.total || undefined} + accent={c.unread > 0} + active={active === `category:${c.id}`} + onClick={() => onChange({ kind: "category", categoryId: c.id })} + /> + )} /> )} @@ -547,7 +549,7 @@ function FolderItem({ function Item({ icon, label, - title, + hideNativeTitle, count, accent, active, @@ -555,8 +557,7 @@ function Item({ }: { icon: React.ReactNode; label: string; - /** Hover explanation, for the automatic labels whose names are not self-evident. */ - title?: string; + hideNativeTitle?: boolean; count?: number; accent?: boolean; active?: boolean; @@ -567,7 +568,7 @@ function Item({ type="button" onClick={onClick} className={cn("group/item", ROW, active ? ROW_ACTIVE : ROW_IDLE)} - title={title || label} + title={hideNativeTitle ? undefined : label} > {icon} diff --git a/web/src/components/app/unibox/ThreadLabelMenu.tsx b/web/src/components/app/unibox/ThreadLabelMenu.tsx index e157217aa..1faf1bb58 100644 --- a/web/src/components/app/unibox/ThreadLabelMenu.tsx +++ b/web/src/components/app/unibox/ThreadLabelMenu.tsx @@ -19,7 +19,7 @@ import { useUserProfile } from "@/hooks/context/user"; import useCreateCategory from "@/lib/api/hooks/app/categories/useCreateCategory"; import useThreadLabels from "@/lib/api/hooks/app/unibox/useThreadLabels"; import useSetThreadLabels from "@/lib/api/hooks/app/unibox/useSetThreadLabels"; -import { tagMeaning } from "@/lib/unibox/tagMeanings"; +import { TagMeaningTooltip } from "@/components/ui/tag-meaning-tooltip"; interface Props { threadId: string; @@ -180,9 +180,9 @@ export function ThreadLabelMenu({ threadId, open, onOpenChange }: Props) { className="size-2.5 rounded-full shrink-0" style={{ backgroundColor: c.color }} /> - - {c.title} - + + {c.title} + {checked && ( assigned )} diff --git a/web/src/components/ui/tag-meaning-tooltip.tsx b/web/src/components/ui/tag-meaning-tooltip.tsx new file mode 100644 index 000000000..69da8b0da --- /dev/null +++ b/web/src/components/ui/tag-meaning-tooltip.tsx @@ -0,0 +1,18 @@ +import type { ReactElement } from "react"; + +import { tagMeaning } from "@/lib/unibox/tagMeanings"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +export function TagMeaningTooltip({ title, children }: { title: string; children: ReactElement }) { + const meaning = tagMeaning(title); + if (!meaning) return children; + + return ( + + {children} + + {meaning} + + + ); +}