feat(layout): sidebar repliable + Gem SVG soignée partout
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 21s
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 21s
Sidebar : nouveau bouton chevron en bas qui replie/déplie (240px ↔ 68px).
Choix persisté en localStorage (`rubis.sidebar.collapsed`). En mode replié :
- Brand → gem seule (28px)
- NavLinks → icône centrée + tooltip Radix au hover qui montre le label
- Compteur rubis → version ultra-compacte (gem 16px + chiffre empilés),
tooltip réaffiche "Rubis ce mois · ≈ Xh libérées"
- Marqueur rubis vertical de l'item actif préservé
Gem : refonte du SVG. 4 facettes triangulaires se rejoignent au centre,
chacune avec une opacité différente (1.0 / 0.8 / 0.65 / 0.48) pour suggérer
le jeu de lumière d'une pierre taillée — sans gradient, qui devient pâteux
à petite taille. Contour propre `strokeLinejoin: round` pour la silhouette.
Drop `logo.png` : `<Brand/>` utilise maintenant la Gem SVG en interne. Plus
aucune dépendance à l'asset PNG bordé/arrondi qui rendait flou aux grosses
tailles. Toutes les surfaces (sidebar, RubisHero, compteur) partagent la
même icône scalable héritant de `currentColor`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b96b62aab6
commit
639191bef9
Binary file not shown.
|
Before Width: | Height: | Size: 846 KiB |
@ -1,22 +1,48 @@
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import logo from "@/assets/logo.png";
|
||||
import { Gem } from "./Gem";
|
||||
|
||||
/**
|
||||
* Lockup horizontal : ◆ + "Rubis" (+ optionnel "sur l'ongle" en suffixe italique muted).
|
||||
* À utiliser dans les headers, le sidebar, les emails.
|
||||
*
|
||||
* Architecture : on s'appuie sur le composant <Gem/> SVG — pas de PNG, pas
|
||||
* de border externe. La pierre EST le logo. Plus rien ne casse à l'export
|
||||
* en taille variable, et le rendu reste identique du SPA aux PDFs aux
|
||||
* favicons (si on génère).
|
||||
*
|
||||
* Cf. /docs/marque.md §2 et le pattern de la landing.
|
||||
*/
|
||||
type BrandProps = {
|
||||
/** Affiche le suffixe "sur l'ongle" en italique muted. */
|
||||
withSuffix?: boolean;
|
||||
/** Taille du gem (le wordmark s'aligne dessus). */
|
||||
/**
|
||||
* Taille de la gem en pixels. Default 22 (lockup) ou 32 (onlyImage).
|
||||
* Pour les usages héros : passer 56-72.
|
||||
*/
|
||||
gemSize?: number;
|
||||
className?: string;
|
||||
/** Mode "logo seul" : que la gem, sans le wordmark. */
|
||||
onlyImage?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function Brand({ withSuffix = false, onlyImage = false, className }: BrandProps) {
|
||||
export function Brand({
|
||||
withSuffix = false,
|
||||
onlyImage = false,
|
||||
gemSize,
|
||||
className,
|
||||
}: BrandProps) {
|
||||
const resolvedSize = gemSize ?? (onlyImage ? 32 : 22);
|
||||
|
||||
if (onlyImage) {
|
||||
return (
|
||||
<Gem
|
||||
size={resolvedSize}
|
||||
className={className}
|
||||
aria-label="Rubis"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
@ -25,26 +51,20 @@ export function Brand({ withSuffix = false, onlyImage = false, className }: Bran
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{onlyImage ? (
|
||||
<img src={logo} alt="Logo" className="w-10 h-10 border-2 border-rubis rounded-xl" />
|
||||
) : (
|
||||
<>
|
||||
<img src={logo} alt="Logo" className="w-8 h-8 border-2 border-rubis rounded-xl" />
|
||||
<span className="leading-none">
|
||||
Rubis
|
||||
{withSuffix && (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-1 font-display italic font-medium text-ink-3",
|
||||
"text-[12.5px] tracking-[-0.005em]",
|
||||
)}
|
||||
>
|
||||
sur l'ongle
|
||||
</span>
|
||||
<Gem size={resolvedSize} />
|
||||
<span className="leading-none">
|
||||
Rubis
|
||||
{withSuffix && (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-1 font-display italic font-medium text-ink-3",
|
||||
"text-[12.5px] tracking-[-0.005em]",
|
||||
)}
|
||||
>
|
||||
sur l'ongle
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,17 +2,25 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* Le ◆ — gem facetté, signature de la marque.
|
||||
* SVG inline (pas une icône Lucide, jamais).
|
||||
*
|
||||
* 4 facettes suggérées + ligne médiane "table" du gem.
|
||||
* Couleur : `currentColor` — hérite du contexte. Default text-rubis.
|
||||
* Construction : losange divisé en 4 facettes triangulaires qui se rencontrent
|
||||
* au centre. Chacune a une opacité différente pour suggérer le jeu de lumière
|
||||
* d'une pierre taillée, sans gradient (qui rend pâteux à petite taille).
|
||||
*
|
||||
* - facette haut-gauche : 100% (lumière)
|
||||
* - facette haut-droite : 80%
|
||||
* - facette bas-gauche : 65%
|
||||
* - facette bas-droite : 48% (zone d'ombre)
|
||||
*
|
||||
* Plus un fin contour qui définit le pourtour. Le tout en `currentColor` —
|
||||
* hérite du parent (default rubis).
|
||||
*
|
||||
* Cf. /docs/marque.md §2 (logo direction A) et §5 (icônes spéciales).
|
||||
*/
|
||||
type GemProps = {
|
||||
/** Taille en pixels (carré). Default 22. */
|
||||
size?: number;
|
||||
/** Si true, applique un drop-shadow doux rubis pour les héros. */
|
||||
/** Drop-shadow doux rubis — pour les usages héros uniquement. */
|
||||
glow?: boolean;
|
||||
className?: string;
|
||||
"aria-label"?: string;
|
||||
@ -23,7 +31,7 @@ export function Gem({ size = 22, glow = false, className, ...props }: GemProps)
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 200 200"
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn(
|
||||
"text-rubis shrink-0",
|
||||
@ -34,12 +42,38 @@ export function Gem({ size = 22, glow = false, className, ...props }: GemProps)
|
||||
aria-hidden={props["aria-label"] ? undefined : "true"}
|
||||
{...props}
|
||||
>
|
||||
<polygon points="100,10 190,100 100,190 10,100" fill="currentColor" />
|
||||
{/* Table du gem */}
|
||||
<line x1="10" y1="100" x2="190" y2="100" stroke="rgba(255,255,255,0.55)" strokeWidth="3" />
|
||||
{/* Facettes hautes */}
|
||||
<line x1="55" y1="55" x2="100" y2="100" stroke="rgba(255,255,255,0.4)" strokeWidth="2" />
|
||||
<line x1="145" y1="55" x2="100" y2="100" stroke="rgba(255,255,255,0.4)" strokeWidth="2" />
|
||||
{/* Facette haut-gauche — la plus claire (côté lumière). */}
|
||||
<polygon
|
||||
points="50,6 6,50 50,50"
|
||||
fill="currentColor"
|
||||
fillOpacity="1"
|
||||
/>
|
||||
{/* Facette haut-droite */}
|
||||
<polygon
|
||||
points="50,6 94,50 50,50"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.8"
|
||||
/>
|
||||
{/* Facette bas-gauche */}
|
||||
<polygon
|
||||
points="6,50 50,50 50,94"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.65"
|
||||
/>
|
||||
{/* Facette bas-droite — la plus sombre (côté ombre). */}
|
||||
<polygon
|
||||
points="50,50 94,50 50,94"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.48"
|
||||
/>
|
||||
{/* Contour propre — épaisseur fixe pour rester lisible à 14px. */}
|
||||
<polygon
|
||||
points="50,6 94,50 50,94 6,50"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.6"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ export function RubisHero({
|
||||
|
||||
<div className="relative z-10 flex flex-col gap-6 sm:flex-row sm:items-center sm:gap-8">
|
||||
<div className="flex shrink-0 items-center gap-4 sm:flex-col sm:items-center sm:gap-1">
|
||||
<Brand onlyImage={true} />
|
||||
<Brand onlyImage gemSize={64} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
import {
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
LayoutDashboard,
|
||||
FileText,
|
||||
ListChecks,
|
||||
@ -12,51 +16,183 @@ import { Brand } from "@/components/brand/Brand";
|
||||
import { Gem } from "@/components/brand/Gem";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { formatRubisToHours } from "@/lib/format";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NavLink } from "./NavLink";
|
||||
|
||||
const STORAGE_KEY = "rubis.sidebar.collapsed";
|
||||
|
||||
/**
|
||||
* Sidebar desktop — 240px wide, sticky.
|
||||
* - Brand en haut
|
||||
* - Nav verticale au centre
|
||||
* - Compteur rubis en bas (gratification permanente, cf. wireframe 4.1)
|
||||
* Sidebar desktop — repliable.
|
||||
*
|
||||
* Le compteur rubis dans le sidebar n'est pas négociable : c'est ce qui
|
||||
* fait dire au user "putain j'ai gagné 24h ce mois".
|
||||
* - Déployée (240px) : brand + nav avec labels + compteur rubis en bas
|
||||
* - Repliée (64px) : juste les icônes (tooltip au hover) + gem du brand
|
||||
*
|
||||
* Le choix est persisté en localStorage (`rubis.sidebar.collapsed`) pour
|
||||
* survivre aux reloads. Toggle via un petit bouton chevron en bas.
|
||||
*
|
||||
* Le compteur rubis disparaît en mode replié (l'écran est trop étroit pour
|
||||
* en faire quelque chose de lisible) — on le retrouve en dépliant. La gem
|
||||
* en haut reste, donc l'identité de marque ne disparaît jamais.
|
||||
*/
|
||||
export function AppSidebar({ rubisThisMonth = 0 }: { rubisThisMonth?: number }) {
|
||||
const { user: _user } = useAuth();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
// Lecture localStorage à l'init (côté client uniquement).
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
setCollapsed(window.localStorage.getItem(STORAGE_KEY) === "1");
|
||||
}, []);
|
||||
|
||||
const toggle = () => {
|
||||
setCollapsed((prev) => {
|
||||
const next = !prev;
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem(STORAGE_KEY, next ? "1" : "0");
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="hidden lg:flex h-screen sticky top-0 w-[240px] shrink-0 flex-col border-r border-line bg-cream-2/60 px-4 py-6">
|
||||
<Link to="/" className="mb-10 px-2">
|
||||
<Brand />
|
||||
<aside
|
||||
className={cn(
|
||||
"hidden lg:flex h-screen sticky top-0 shrink-0 flex-col border-r border-line bg-cream-2/60 py-6",
|
||||
"transition-[width,padding] duration-200 ease-out",
|
||||
collapsed ? "w-[68px] px-2" : "w-[240px] px-4",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className={cn(
|
||||
"mb-10 flex items-center justify-center",
|
||||
collapsed ? "px-0" : "px-2 justify-start",
|
||||
)}
|
||||
aria-label="Accueil Rubis"
|
||||
>
|
||||
{collapsed ? <Brand onlyImage gemSize={28} /> : <Brand />}
|
||||
</Link>
|
||||
|
||||
<nav className="flex flex-col gap-1">
|
||||
<NavLink to="/" icon={<LayoutDashboard size={17} />} label="Dashboard" />
|
||||
<NavLink to="/factures" icon={<FileText size={17} />} label="Factures" />
|
||||
<NavLink to="/plans" icon={<ListChecks size={17} />} label="Plans de relance" />
|
||||
<NavLink to="/clients" icon={<Users size={17} />} label="Clients" />
|
||||
<NavLink to="/insights" icon={<TrendingUp size={17} />} label="Insights" />
|
||||
<NavLink to="/parametres" icon={<Settings size={17} />} label="Paramètres" />
|
||||
<NavLink
|
||||
to="/"
|
||||
icon={<LayoutDashboard size={17} />}
|
||||
label="Dashboard"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<NavLink
|
||||
to="/factures"
|
||||
icon={<FileText size={17} />}
|
||||
label="Factures"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<NavLink
|
||||
to="/plans"
|
||||
icon={<ListChecks size={17} />}
|
||||
label="Plans de relance"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<NavLink
|
||||
to="/clients"
|
||||
icon={<Users size={17} />}
|
||||
label="Clients"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<NavLink
|
||||
to="/insights"
|
||||
icon={<TrendingUp size={17} />}
|
||||
label="Insights"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<NavLink
|
||||
to="/parametres"
|
||||
icon={<Settings size={17} />}
|
||||
label="Paramètres"
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto">
|
||||
<div className="rounded-soft border border-line bg-white px-3.5 py-3">
|
||||
<p className="text-[10.5px] font-semibold uppercase tracking-[0.12em] text-ink-3">
|
||||
Rubis ce mois
|
||||
</p>
|
||||
<div className="mt-1.5 flex items-end gap-2">
|
||||
<Gem size={18} />
|
||||
<span className="font-display text-[22px] font-bold leading-none tabular-nums">
|
||||
{rubisThisMonth}
|
||||
</span>
|
||||
<div className="mt-auto flex flex-col gap-3">
|
||||
{collapsed ? (
|
||||
<RubisCounterCompact value={rubisThisMonth} />
|
||||
) : (
|
||||
<div className="rounded-soft border border-line bg-white px-3.5 py-3">
|
||||
<p className="text-[10.5px] font-semibold uppercase tracking-[0.12em] text-ink-3">
|
||||
Rubis ce mois
|
||||
</p>
|
||||
<div className="mt-1.5 flex items-end gap-2">
|
||||
<Gem size={18} />
|
||||
<span className="font-display text-[22px] font-bold leading-none tabular-nums">
|
||||
{rubisThisMonth}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-ink-3">
|
||||
≈ {formatRubisToHours(rubisThisMonth)} libérées
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-ink-3">
|
||||
≈ {formatRubisToHours(rubisThisMonth)} libérées
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Toggle replier / déplier */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
aria-label={collapsed ? "Déplier la sidebar" : "Replier la sidebar"}
|
||||
title={collapsed ? "Déplier" : "Replier"}
|
||||
className={cn(
|
||||
"flex h-8 items-center justify-center rounded-default border border-line bg-white text-ink-3 cursor-pointer",
|
||||
"transition-colors hover:text-rubis hover:border-rubis",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rubis-glow",
|
||||
)}
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronsRight size={14} aria-hidden="true" />
|
||||
) : (
|
||||
<ChevronsLeft size={14} aria-hidden="true" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compteur rubis ultra-compact pour le mode replié — gem + chiffre empilés
|
||||
* verticalement, tooltip au hover qui rappelle "Rubis ce mois · ≈ Xh libérées".
|
||||
*/
|
||||
function RubisCounterCompact({ value }: { value: number }) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider delayDuration={200}>
|
||||
<TooltipPrimitive.Root>
|
||||
<TooltipPrimitive.Trigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center gap-0.5 rounded-soft border border-line bg-white py-2",
|
||||
"cursor-default",
|
||||
)}
|
||||
role="status"
|
||||
aria-label={`${value} rubis ce mois, soit ${formatRubisToHours(value)} libérées`}
|
||||
>
|
||||
<Gem size={16} />
|
||||
<span className="font-display text-[15px] font-bold leading-none tabular-nums text-ink">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipPrimitive.Trigger>
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
side="right"
|
||||
sideOffset={8}
|
||||
className={cn(
|
||||
"z-50 rounded-default bg-ink px-2.5 py-1.5 text-[12px] font-medium text-white shadow-soft",
|
||||
"data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95",
|
||||
)}
|
||||
>
|
||||
<span className="font-semibold">Rubis ce mois</span>
|
||||
<span className="text-ink-3"> · ≈ {formatRubisToHours(value)} libérées</span>
|
||||
<TooltipPrimitive.Arrow className="fill-ink" width={8} height={4} />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</TooltipPrimitive.Root>
|
||||
</TooltipPrimitive.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import { Link, type LinkProps } from "@tanstack/react-router";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* NavLink — item de nav du sidebar / tab bar.
|
||||
*
|
||||
* État actif : bord gauche rubis (desktop) ou label rubis (tab bar).
|
||||
* Pas de bg plein quand inactif, le sidebar reste calme.
|
||||
*
|
||||
* En mode `collapsed` (sidebar repliée), seul l'icône est rendue — le
|
||||
* label devient un tooltip qui apparaît au hover. Garde l'identité de
|
||||
* navigation tout en récupérant ~170px de surface utile.
|
||||
*/
|
||||
type Variant = "sidebar" | "tab-bar";
|
||||
|
||||
@ -12,20 +18,37 @@ type NavLinkProps = Omit<LinkProps, "children"> & {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
variant?: Variant;
|
||||
/** Sidebar uniquement : repliée → icône seule + tooltip. */
|
||||
collapsed?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function NavLink({ icon, label, variant = "sidebar", className, ...linkProps }: NavLinkProps) {
|
||||
export function NavLink({
|
||||
icon,
|
||||
label,
|
||||
variant = "sidebar",
|
||||
collapsed = false,
|
||||
className,
|
||||
...linkProps
|
||||
}: NavLinkProps) {
|
||||
const isSidebar = variant === "sidebar";
|
||||
return (
|
||||
const isCollapsed = isSidebar && collapsed;
|
||||
|
||||
const link = (
|
||||
<Link
|
||||
{...linkProps}
|
||||
activeOptions={{ exact: linkProps.to === "/" }}
|
||||
aria-label={isCollapsed ? label : undefined}
|
||||
className={cn(
|
||||
"group relative flex items-center transition-colors duration-150",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rubis-glow",
|
||||
isSidebar
|
||||
? "gap-3 rounded-default px-3 py-2.5 text-[14px] font-medium text-ink-2 hover:text-ink hover:bg-cream"
|
||||
? cn(
|
||||
"rounded-default text-[14px] font-medium text-ink-2 hover:text-ink hover:bg-cream",
|
||||
isCollapsed
|
||||
? "justify-center py-2.5"
|
||||
: "gap-3 px-3 py-2.5",
|
||||
)
|
||||
: "flex-1 flex-col gap-0.5 py-2 text-[10.5px] font-semibold text-ink-3 hover:text-ink",
|
||||
className,
|
||||
)}
|
||||
@ -35,7 +58,7 @@ export function NavLink({ icon, label, variant = "sidebar", className, ...linkPr
|
||||
: "text-rubis",
|
||||
}}
|
||||
>
|
||||
{/* Marqueur rubis vertical sur le sidebar quand actif */}
|
||||
{/* Marqueur rubis vertical (sidebar actif) — préservé en mode replié. */}
|
||||
{isSidebar && (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -49,12 +72,41 @@ export function NavLink({ icon, label, variant = "sidebar", className, ...linkPr
|
||||
<span
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
isSidebar ? "text-ink-3 group-aria-[current=page]:text-rubis" : "text-current",
|
||||
isSidebar
|
||||
? "text-ink-3 group-aria-[current=page]:text-rubis"
|
||||
: "text-current",
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
<span className={cn(isSidebar ? "" : "leading-none")}>{label}</span>
|
||||
{!isCollapsed && (
|
||||
<span className={cn(isSidebar ? "" : "leading-none")}>{label}</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
|
||||
// En mode replié, on wrappe dans un Tooltip Radix qui montre le label
|
||||
// côté droit au hover. En déployé : pas de tooltip (label déjà visible).
|
||||
if (!isCollapsed) return link;
|
||||
|
||||
return (
|
||||
<TooltipPrimitive.Provider delayDuration={200}>
|
||||
<TooltipPrimitive.Root>
|
||||
<TooltipPrimitive.Trigger asChild>{link}</TooltipPrimitive.Trigger>
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
side="right"
|
||||
sideOffset={8}
|
||||
className={cn(
|
||||
"z-50 rounded-default bg-ink px-2.5 py-1.5 text-[12px] font-medium text-white shadow-soft",
|
||||
"data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95",
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
<TooltipPrimitive.Arrow className="fill-ink" width={8} height={4} />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</TooltipPrimitive.Root>
|
||||
</TooltipPrimitive.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user