mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 16:02:02 +00:00
feat: render automatic inbox label explanations with themed tooltips
This commit is contained in:
@@ -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<string, string> = {
|
||||
now: "bg-rose-50 text-rose-700",
|
||||
@@ -83,13 +83,11 @@ function Row({ r }: { r: InboxTagRow }) {
|
||||
<span className="text-[11.5px] text-slate-400">no labels</span>
|
||||
) : (
|
||||
r.labels.map((l) => (
|
||||
<span
|
||||
key={l}
|
||||
title={tagMeaning(l) || l}
|
||||
className="px-1.5 rounded bg-slate-100 text-slate-700 text-[11px] cursor-help"
|
||||
>
|
||||
{l}
|
||||
</span>
|
||||
<TagMeaningTooltip key={l} title={l}>
|
||||
<span className="px-1.5 rounded bg-slate-100 text-slate-700 text-[11px] cursor-help">
|
||||
{l}
|
||||
</span>
|
||||
</TagMeaningTooltip>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<span
|
||||
title={meaning || category.title}
|
||||
className={`inline-flex items-center gap-1 min-w-0 ${compact ? "h-4 pl-1 pr-1 text-[10px]" : "h-5 pl-1.5 pr-1 text-[11px]"} rounded font-medium`}
|
||||
style={{
|
||||
backgroundColor: hexToRgba(category.color, 0.12),
|
||||
color: category.color,
|
||||
border: `1px solid ${hexToRgba(category.color, 0.25)}`,
|
||||
}}
|
||||
>
|
||||
<TagMeaningTooltip title={category.title}>
|
||||
<span
|
||||
className={`${compact ? "size-1.5" : "size-2"} rounded-full shrink-0`}
|
||||
style={{ backgroundColor: category.color }}
|
||||
/>
|
||||
<span
|
||||
className="truncate min-w-0 max-w-[72px] md:max-w-none"
|
||||
{...clippedTitle}
|
||||
className={`inline-flex items-center gap-1 min-w-0 ${compact ? "h-4 pl-1 pr-1 text-[10px]" : "h-5 pl-1.5 pr-1 text-[11px]"} rounded font-medium`}
|
||||
style={{
|
||||
backgroundColor: hexToRgba(category.color, 0.12),
|
||||
color: category.color,
|
||||
border: `1px solid ${hexToRgba(category.color, 0.25)}`,
|
||||
}}
|
||||
>
|
||||
{category.title}
|
||||
</span>
|
||||
{onRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
className="opacity-70 hover:opacity-100"
|
||||
aria-label={`Remove ${category.title}`}
|
||||
<span
|
||||
className={`${compact ? "size-1.5" : "size-2"} rounded-full shrink-0`}
|
||||
style={{ backgroundColor: category.color }}
|
||||
/>
|
||||
<span
|
||||
className="truncate min-w-0 max-w-[72px] md:max-w-none"
|
||||
{...clippedTitle}
|
||||
>
|
||||
<XIcon className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
{category.title}
|
||||
</span>
|
||||
{onRemove && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
}}
|
||||
className="opacity-70 hover:opacity-100"
|
||||
aria-label={`Remove ${category.title}`}
|
||||
>
|
||||
<XIcon className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
</TagMeaningTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => (
|
||||
<Item
|
||||
key={c.id}
|
||||
icon={<Dot color={c.color} />}
|
||||
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 })}
|
||||
/>
|
||||
<TagMeaningTooltip key={c.id} title={c.title}>
|
||||
<Item
|
||||
icon={<Dot color={c.color} />}
|
||||
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 })}
|
||||
/>
|
||||
</TagMeaningTooltip>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -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}
|
||||
>
|
||||
<span className={cn("shrink-0", active ? "text-slate-800" : "text-slate-400 group-hover/item:text-slate-600")}>
|
||||
{icon}
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
<span className="truncate" title={tagMeaning(c.title) || undefined}>
|
||||
{c.title}
|
||||
</span>
|
||||
<TagMeaningTooltip title={c.title}>
|
||||
<span className="truncate">{c.title}</span>
|
||||
</TagMeaningTooltip>
|
||||
{checked && (
|
||||
<span className="ml-auto text-[10px] text-slate-300">assigned</span>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent sideOffset={6} className="max-w-72">
|
||||
{meaning}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user