Added Tooltip Component
This commit is contained in:
@@ -21,6 +21,7 @@ export { default as PasswordField } from "../src/components/PasswordField.astro"
|
||||
export { default as ThemeSwitcher } from "../src/components/ThemeSwitcher.astro";
|
||||
export { default as TimePicker } from "../src/components/TimePicker.astro";
|
||||
export { default as Drawer } from "../src/components/Drawer.astro";
|
||||
export { default as Tooltip } from "../src/components/Tooltip.astro";
|
||||
export { cn } from "../src/utils/cn.js";
|
||||
`;
|
||||
|
||||
@@ -41,6 +42,7 @@ export const PasswordField: any;
|
||||
export const ThemeSwitcher: any;
|
||||
export const TimePicker: any;
|
||||
export const Drawer: any;
|
||||
export const Tooltip: any;
|
||||
export function cn(...classes: Array<string | number | false | null | undefined>): string;
|
||||
`;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface Props {
|
||||
|
||||
const {
|
||||
position = "right",
|
||||
size = "320px",
|
||||
size = "384px",
|
||||
overlay = true,
|
||||
closeOnOverlayClick = true,
|
||||
closeOnEsc = true,
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
|
||||
type Placement =
|
||||
| "bottom-start"
|
||||
| "bottom"
|
||||
| "bottom-end"
|
||||
| "top-start"
|
||||
| "top"
|
||||
| "top-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "right-end"
|
||||
| "left-start"
|
||||
| "left"
|
||||
| "left-end";
|
||||
|
||||
interface Props extends HTMLAttributes<"div"> {
|
||||
text?: string; // simple string; or use <slot name="content">
|
||||
placement?: Placement; // default: "top"
|
||||
offset?: number; // gap between trigger & tooltip (default 10)
|
||||
shiftPadding?: number; // viewport padding (default 8)
|
||||
showArrow?: boolean; // default true
|
||||
delayOpen?: number; // ms (default 80)
|
||||
delayClose?: number; // ms (default 80)
|
||||
interactive?: boolean; // hoverable panel (default false)
|
||||
disabled?: boolean; // default false
|
||||
maxWidth?: number | string; // e.g. 280 or "20rem" (default "18rem")
|
||||
openOn?: "hover" | "focus" | "hover-focus"; // default "hover-focus"
|
||||
}
|
||||
|
||||
const {
|
||||
text,
|
||||
placement = "top",
|
||||
offset = 10,
|
||||
shiftPadding = 8,
|
||||
showArrow = true,
|
||||
delayOpen = 80,
|
||||
delayClose = 80,
|
||||
interactive = false,
|
||||
disabled = false,
|
||||
maxWidth = "18rem",
|
||||
openOn = "hover",
|
||||
class: className,
|
||||
...rest
|
||||
}: Props = Astro.props;
|
||||
|
||||
const id = `tt-${Math.random().toString(36).slice(2)}`;
|
||||
---
|
||||
|
||||
<div
|
||||
id={id}
|
||||
class={["wr:relative wr:inline-flex", className].join(" ")}
|
||||
{...rest}
|
||||
data-tt-root
|
||||
data-placement={placement}
|
||||
data-offset={offset}
|
||||
data-shift-padding={shiftPadding}
|
||||
data-show-arrow={showArrow ? "1" : ""}
|
||||
data-delay-open={delayOpen}
|
||||
data-delay-close={delayClose}
|
||||
data-interactive={interactive ? "1" : ""}
|
||||
data-open-on={openOn}
|
||||
data-disabled={disabled ? "1" : ""}
|
||||
>
|
||||
<!-- Unstyled trigger wrapper (keeps a single trigger, no duplicate UI) -->
|
||||
<span
|
||||
data-tt-trigger
|
||||
class="wr:inline-flex wr:align-middle wr:cursor-help wr:select-none"
|
||||
tabindex={disabled ? undefined : 0}
|
||||
aria-describedby={`${id}-tip`}
|
||||
aria-expanded="false"
|
||||
>
|
||||
<slot name="trigger" />
|
||||
</span>
|
||||
|
||||
<!-- Portaled tooltip panel (moved to body as fixed) -->
|
||||
<div
|
||||
class="wr:hidden wr:z-50 wr:max-w-[--tt-max] wr:rounded-xl wr:border wr:border-border
|
||||
wr:bg-popover/95 wr:backdrop-blur-md wr:text-popover-foreground
|
||||
wr:px-3 wr:py-2 wr:text-xs wr:leading-5
|
||||
wr:shadow-[0_18px_50px_-15px_rgba(0,0,0,0.35),0_6px_20px_-16px_rgba(0,0,0,0.25)]
|
||||
wr:origin-[var(--tt-origin,center)] wr:translate-y-1 wr:scale-95 wr:opacity-0
|
||||
wr:transition wr:duration-150 wr:ease-out"
|
||||
data-tt-panel
|
||||
style={{
|
||||
"--tt-max":
|
||||
typeof maxWidth === "number"
|
||||
? `${maxWidth}px`
|
||||
: String(maxWidth),
|
||||
}}
|
||||
role="tooltip"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{showArrow && <div data-tt-arrow class="wr:absolute wr:size-2" />}
|
||||
{text ? <div class="wr:whitespace-pre-wrap">{text}</div> : <slot />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-tt-panel] {
|
||||
--ar: 8px;
|
||||
} /* arrow size */
|
||||
|
||||
/* Triangular arrow that points the right way based on side */
|
||||
[data-tt-panel][data-side="bottom"] [data-tt-arrow] {
|
||||
position: absolute;
|
||||
top: calc(var(--ar) * -1);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: var(--ar) solid transparent;
|
||||
border-right: var(--ar) solid transparent;
|
||||
border-bottom: var(--ar) solid hsl(var(--popover)); /* points UP */
|
||||
filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
[data-tt-panel][data-side="top"] [data-tt-arrow] {
|
||||
position: absolute;
|
||||
bottom: calc(var(--ar) * -1);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: var(--ar) solid transparent;
|
||||
border-right: var(--ar) solid transparent;
|
||||
border-top: var(--ar) solid hsl(var(--popover)); /* points DOWN */
|
||||
filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
[data-tt-panel][data-side="right"] [data-tt-arrow] {
|
||||
position: absolute;
|
||||
left: calc(var(--ar) * -1);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: var(--ar) solid transparent;
|
||||
border-bottom: var(--ar) solid transparent;
|
||||
border-right: var(--ar) solid hsl(var(--popover)); /* points LEFT->RIGHT */
|
||||
filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
[data-tt-panel][data-side="left"] [data-tt-arrow] {
|
||||
position: absolute;
|
||||
right: calc(var(--ar) * -1);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: var(--ar) solid transparent;
|
||||
border-bottom: var(--ar) solid transparent;
|
||||
border-left: var(--ar) solid hsl(var(--popover)); /* points RIGHT->LEFT */
|
||||
filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
/* Open state animation stays the same */
|
||||
[data-tt-panel].is-open {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module" is:inline define:vars={{ id }}>
|
||||
(() => {
|
||||
const root = document.getElementById(id);
|
||||
if (!root) return;
|
||||
|
||||
const trigger = root.querySelector("[data-tt-trigger]");
|
||||
const panel = root.querySelector("[data-tt-panel]");
|
||||
const arrowEl = panel?.querySelector("[data-tt-arrow]");
|
||||
|
||||
const disabled = !!root.dataset.disabled;
|
||||
const placement = root.dataset.placement || "top";
|
||||
const offset = Number(root.dataset.offset || 10);
|
||||
const pad = Number(root.dataset.shiftPadding || 8);
|
||||
const showArrow = !!root.dataset.showArrow;
|
||||
const delayOpen = Number(root.dataset.delayOpen || 80);
|
||||
const delayClose = Number(root.dataset.delayClose || 80);
|
||||
const interactive = !!root.dataset.interactive;
|
||||
const openOn = root.dataset.openOn || "hover-focus";
|
||||
|
||||
// Portal (fixed) for robust positioning
|
||||
const portal = document.createElement("div");
|
||||
portal.style.position = "fixed";
|
||||
document.body.appendChild(portal);
|
||||
panel.style.position = "fixed";
|
||||
portal.appendChild(panel);
|
||||
|
||||
let open = false,
|
||||
openT = 0,
|
||||
closeT = 0;
|
||||
|
||||
const sideOf = (p) => p.split("-")[0];
|
||||
const alignOf = (p) => p.split("-")[1] || "center";
|
||||
|
||||
function setOriginFor(side, align) {
|
||||
const mapSide =
|
||||
{ top: "bottom", bottom: "top", left: "right", right: "left" }[
|
||||
side
|
||||
] || "top";
|
||||
const mapAlign =
|
||||
side === "top" || side === "bottom"
|
||||
? align === "start"
|
||||
? "left"
|
||||
: align === "end"
|
||||
? "right"
|
||||
: "center"
|
||||
: align === "start"
|
||||
? "top"
|
||||
: align === "end"
|
||||
? "bottom"
|
||||
: "center";
|
||||
panel.style.setProperty("--tt-origin", `${mapSide} ${mapAlign}`);
|
||||
}
|
||||
|
||||
function placeOnce(pref) {
|
||||
const t = trigger.getBoundingClientRect();
|
||||
const p = panel.getBoundingClientRect();
|
||||
const vw = document.documentElement.clientWidth;
|
||||
const vh = document.documentElement.clientHeight;
|
||||
|
||||
const side = sideOf(pref);
|
||||
const align = alignOf(pref);
|
||||
let x = 0,
|
||||
y = 0;
|
||||
|
||||
if (side === "bottom") y = t.bottom + offset;
|
||||
else if (side === "top") y = t.top - p.height - offset;
|
||||
else if (side === "right") x = t.right + offset;
|
||||
else if (side === "left") x = t.left - p.width - offset;
|
||||
|
||||
if (side === "top" || side === "bottom") {
|
||||
if (align === "start") x = t.left;
|
||||
else if (align === "end") x = t.right - p.width;
|
||||
else x = t.left + (t.width - p.width) / 2;
|
||||
} else {
|
||||
if (align === "start") y = t.top;
|
||||
else if (align === "end") y = t.bottom - p.height;
|
||||
else y = t.top + (t.height - p.height) / 2;
|
||||
}
|
||||
|
||||
x = Math.max(pad, Math.min(x, vw - p.width - pad));
|
||||
y = Math.max(pad, Math.min(y, vh - p.height - pad));
|
||||
return { x: Math.round(x), y: Math.round(y) };
|
||||
}
|
||||
|
||||
function smartPosition() {
|
||||
const prefSide = sideOf(placement);
|
||||
const prefAlign = alignOf(placement);
|
||||
const flips = {
|
||||
top: "bottom",
|
||||
bottom: "top",
|
||||
left: "right",
|
||||
right: "left",
|
||||
};
|
||||
const candidates = [
|
||||
placement,
|
||||
`${flips[prefSide]}${prefAlign ? `-${prefAlign}` : ""}`,
|
||||
];
|
||||
|
||||
if (prefSide === "top" || prefSide === "bottom") {
|
||||
candidates.push(
|
||||
`left-${prefAlign || "start"}`,
|
||||
`right-${prefAlign || "start"}`,
|
||||
);
|
||||
} else {
|
||||
candidates.push(
|
||||
`top-${prefAlign || "start"}`,
|
||||
`bottom-${prefAlign || "start"}`,
|
||||
);
|
||||
}
|
||||
|
||||
const vw = document.documentElement.clientWidth;
|
||||
const vh = document.documentElement.clientHeight;
|
||||
let best = null;
|
||||
|
||||
// Temporarily make visible to measure accurately
|
||||
panel.style.visibility = "hidden";
|
||||
panel.classList.remove("wr:hidden");
|
||||
const measure = () => {
|
||||
for (const cand of candidates) {
|
||||
const pos = placeOnce(cand);
|
||||
const p = panel.getBoundingClientRect();
|
||||
const over =
|
||||
Math.max(0, -pos.x) +
|
||||
Math.max(0, -pos.y) +
|
||||
Math.max(0, pos.x + p.width - vw) +
|
||||
Math.max(0, pos.y + p.height - vh);
|
||||
if (!best || over < best.score)
|
||||
best = { cand, ...pos, score: over };
|
||||
}
|
||||
};
|
||||
measure();
|
||||
panel.style.visibility = "";
|
||||
return best;
|
||||
}
|
||||
|
||||
function positionPanel() {
|
||||
if (!open) return;
|
||||
|
||||
const best = smartPosition();
|
||||
panel.style.left = best.x + "px";
|
||||
panel.style.top = best.y + "px";
|
||||
|
||||
const side = sideOf(best.cand);
|
||||
const align = alignOf(best.cand);
|
||||
setOriginFor(side, align);
|
||||
panel.dataset.side = side;
|
||||
|
||||
if (showArrow && arrowEl) {
|
||||
Object.assign(arrowEl.style, {
|
||||
left: "",
|
||||
top: "",
|
||||
right: "",
|
||||
bottom: "",
|
||||
});
|
||||
const t = trigger.getBoundingClientRect();
|
||||
const p = panel.getBoundingClientRect();
|
||||
const aw = arrowEl.getBoundingClientRect().width;
|
||||
const ah = arrowEl.getBoundingClientRect().height;
|
||||
const AR = 7;
|
||||
|
||||
if (side === "bottom") {
|
||||
arrowEl.style.top = -AR + "px";
|
||||
arrowEl.style.left =
|
||||
Math.round(t.left + t.width / 2 - p.left - aw / 2) +
|
||||
"px";
|
||||
} else if (side === "top") {
|
||||
arrowEl.style.bottom = -AR + "px";
|
||||
arrowEl.style.left =
|
||||
Math.round(t.left + t.width / 2 - p.left - aw / 2) +
|
||||
"px";
|
||||
} else if (side === "right") {
|
||||
arrowEl.style.left = -AR + "px";
|
||||
arrowEl.style.top =
|
||||
Math.round(t.top + t.height / 2 - p.top - ah / 2) +
|
||||
"px";
|
||||
} else if (side === "left") {
|
||||
arrowEl.style.right = -AR + "px";
|
||||
arrowEl.style.top =
|
||||
Math.round(t.top + t.height / 2 - p.top - ah / 2) +
|
||||
"px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setOpen(v) {
|
||||
if (disabled || open === v) return;
|
||||
open = v;
|
||||
trigger.setAttribute("aria-expanded", String(open));
|
||||
|
||||
if (open) {
|
||||
panel.classList.remove("wr:hidden");
|
||||
requestAnimationFrame(() => {
|
||||
panel.classList.add("is-open");
|
||||
panel.classList.remove(
|
||||
"wr:translate-y-1",
|
||||
"wr:scale-95",
|
||||
"wr:opacity-0",
|
||||
);
|
||||
positionPanel();
|
||||
});
|
||||
window.addEventListener("resize", positionPanel, {
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener("scroll", positionPanel, {
|
||||
passive: true,
|
||||
});
|
||||
} else {
|
||||
panel.classList.remove("is-open");
|
||||
panel.classList.add(
|
||||
"wr:translate-y-1",
|
||||
"wr:scale-95",
|
||||
"wr:opacity-0",
|
||||
);
|
||||
setTimeout(() => panel.classList.add("wr:hidden"), 140);
|
||||
window.removeEventListener("resize", positionPanel);
|
||||
window.removeEventListener("scroll", positionPanel);
|
||||
}
|
||||
}
|
||||
|
||||
const openLater = () => {
|
||||
clearTimeout(closeT);
|
||||
clearTimeout(openT);
|
||||
openT = setTimeout(() => setOpen(true), delayOpen);
|
||||
};
|
||||
const closeLater = () => {
|
||||
clearTimeout(openT);
|
||||
clearTimeout(closeT);
|
||||
closeT = setTimeout(() => setOpen(false), delayClose);
|
||||
};
|
||||
|
||||
// Wire triggers
|
||||
if (openOn === "hover" || openOn === "hover-focus") {
|
||||
trigger.addEventListener("pointerenter", openLater);
|
||||
trigger.addEventListener("pointerleave", closeLater);
|
||||
if (interactive) {
|
||||
panel.addEventListener("pointerenter", () => {
|
||||
clearTimeout(closeT);
|
||||
});
|
||||
panel.addEventListener("pointerleave", closeLater);
|
||||
}
|
||||
}
|
||||
if (openOn === "focus" || openOn === "hover-focus") {
|
||||
trigger.addEventListener("focus", openLater);
|
||||
trigger.addEventListener("blur", closeLater);
|
||||
}
|
||||
|
||||
if (openOn === "click") {
|
||||
trigger.addEventListener("click", () => setOpen(!open));
|
||||
document.addEventListener("pointerdown", (e) => {
|
||||
if (!root.contains(e.target) && !panel.contains(e.target))
|
||||
setOpen(false);
|
||||
});
|
||||
document.addEventListener(
|
||||
"keydown",
|
||||
(e) => e.key === "Escape" && setOpen(false),
|
||||
);
|
||||
}
|
||||
|
||||
// Close on outside pointerdown
|
||||
document.addEventListener("pointerdown", (e) => {
|
||||
if (!open) return;
|
||||
if (root.contains(e.target) || panel.contains(e.target)) return;
|
||||
setOpen(false);
|
||||
});
|
||||
|
||||
// Cleanup on client navigation
|
||||
document.addEventListener("astro:before-swap", () => {
|
||||
try {
|
||||
panel.remove();
|
||||
} catch {}
|
||||
try {
|
||||
portal.remove();
|
||||
} catch {}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -14,6 +14,7 @@ export { default as PasswordField } from "../src/components/PasswordField.astro"
|
||||
export { default as ThemeSwitcher } from "../src/components/ThemeSwitcher.astro";
|
||||
export { default as TimePicker } from "../src/components/TimePicker.astro";
|
||||
export { default as Drawer } from "../src/components/Drawer.astro";
|
||||
export { default as Tooltip } from "../src/components/Tooltip.astro";
|
||||
|
||||
// Utils
|
||||
export { cn } from "./utils/cn";
|
||||
@@ -56,6 +56,9 @@ import Base from "./Base.astro";
|
||||
<li class="list-item">
|
||||
<a href="/drawer">Drawer</a>
|
||||
</li>
|
||||
<li class="list-item">
|
||||
<a href="/tooltip">Tooltip</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
---
|
||||
import ComponentLayout from "../layouts/ComponentLayout.astro";
|
||||
import Tooltip from "../components/Tooltip.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
---
|
||||
|
||||
<ComponentLayout>
|
||||
<div class="wr:flex wr:flex-col wr:gap-4">
|
||||
<Tooltip text="Copy to clipboard">
|
||||
<button
|
||||
slot="trigger"
|
||||
class="wr:rounded-md wr:border wr:px-3 wr:py-2">Copy</button
|
||||
>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip interactive={true} placement="bottom" delayOpen={120}>
|
||||
<span slot="trigger">Pricing</span>
|
||||
<div class="wr:flex wr:flex-col wr:gap-1">
|
||||
<div class="wr:font-medium">Pro plan</div>
|
||||
<div class="wr:text-xs wr:text-muted-foreground">
|
||||
Unlimited projects, priority support.
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
openOn="hover"
|
||||
placement="right-start"
|
||||
offset={12}
|
||||
showArrow
|
||||
interactive={true}
|
||||
>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="wr:inline-flex wr:items-center wr:gap-2 wr:h-9 wr:px-3 wr:rounded-full wr:bg-background/70 wr:backdrop-blur-sm wr:ring-1 wr:ring-border/60 hover:wr:ring-border wr:text-sm"
|
||||
>
|
||||
<Icon name="mdi:eye" class="wr:size-4" />
|
||||
Quick preview
|
||||
</button>
|
||||
|
||||
<div class="wr:w-64 wr:rounded-xl wr:overflow-hidden">
|
||||
<img
|
||||
src="/images/preview.jpg"
|
||||
alt=""
|
||||
class="wr:w-full wr:h-28 wr:object-cover"
|
||||
/>
|
||||
<div class="wr:p-3 wr:space-y-2">
|
||||
<div class="wr:text-sm wr:font-semibold">
|
||||
Starter Template
|
||||
</div>
|
||||
<p class="wr:text-xs wr:text-muted-foreground">
|
||||
Clean, responsive template with auth, dashboard &
|
||||
billing.
|
||||
</p>
|
||||
<div class="wr:flex wr:gap-2">
|
||||
<button
|
||||
class="wr:h-8 wr:px-3 wr:text-xs wr:rounded-md wr:bg-primary wr:text-primary-foreground hover:wr:bg-primary/90"
|
||||
>Use</button
|
||||
>
|
||||
<button
|
||||
class="wr:h-8 wr:px-3 wr:text-xs wr:rounded-md wr:border wr:border-border hover:wr:bg-muted/60"
|
||||
>Details</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="wr:block wr:text-sm wr:font-medium wr:mb-1 wr:text-foreground"
|
||||
>
|
||||
Password
|
||||
<Tooltip
|
||||
openOn="click"
|
||||
placement="top-end"
|
||||
offset={10}
|
||||
showArrow
|
||||
>
|
||||
<button
|
||||
slot="trigger"
|
||||
type="button"
|
||||
class="wr:align-middle wr:ml-1 wr:inline-flex wr:items-center wr:justify-center wr:size-5 wr:rounded-full wr:ring-1 wr:ring-border/60 hover:wr:ring-border"
|
||||
aria-label="Password rules"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:information"
|
||||
class="wr:size-3.5 wr:opacity-80"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div class="wr:w-64 wr:p-3 wr:text-sm wr:space-y-2">
|
||||
<div class="wr:font-medium">Must include:</div>
|
||||
<ul class="wr:space-y-1 wr:text-xs">
|
||||
<li class="wr:flex wr:items-center wr:gap-2">
|
||||
<span
|
||||
class="wr:size-1.5 wr:rounded-full wr:bg-danger/70"
|
||||
></span> 8+ characters
|
||||
</li>
|
||||
<li class="wr:flex wr:items-center wr:gap-2">
|
||||
<span
|
||||
class="wr:size-1.5 wr:rounded-full wr:bg-danger/70"
|
||||
></span> Upper & lower case
|
||||
</li>
|
||||
<li class="wr:flex wr:items-center wr:gap-2">
|
||||
<span
|
||||
class="wr:size-1.5 wr:rounded-full wr:bg-danger/70"
|
||||
></span> 1+ number or symbol
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
class="wr:h-11 wr:px-3 wr:w-full wr:rounded-md wr:border wr:border-border wr:bg-background wr:text-sm wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
openOn="contextmenu"
|
||||
placement="bottom-start"
|
||||
offset={8}
|
||||
showArrow
|
||||
>
|
||||
<div
|
||||
slot="trigger"
|
||||
class="wr:rounded-lg wr:border wr:border-border wr:p-4 wr:bg-card wr:text-card-foreground wr:cursor-context-menu"
|
||||
>
|
||||
<div class="wr:font-medium wr:text-sm">Report.pdf</div>
|
||||
<div class="wr:text-xs wr:text-muted-foreground">
|
||||
1.2 MB · Updated today
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wr:w-52 wr:py-1">
|
||||
<button
|
||||
data-dd-item
|
||||
class="wr:w-full wr:flex wr:items-center wr:gap-2 wr:px-3 wr:py-2 wr:text-sm wr:rounded-md hover:wr:bg-muted/60"
|
||||
>
|
||||
<Icon name="mdi:download" class="wr:size-4" /> Download
|
||||
</button>
|
||||
<button
|
||||
data-dd-item
|
||||
class="wr:w-full wr:flex wr:items-center wr:gap-2 wr:px-3 wr:py-2 wr:text-sm wr:rounded-md hover:wr:bg-muted/60"
|
||||
>
|
||||
<Icon name="mdi:pencil" class="wr:size-4" /> Rename
|
||||
</button>
|
||||
<div class="wr:h-px wr:bg-border/60 wr:my-1"></div>
|
||||
<button
|
||||
data-dd-item
|
||||
class="wr:w-full wr:flex wr:items-center wr:gap-2 wr:px-3 wr:py-2 wr:text-sm wr:text-danger wr:rounded-md hover:wr:bg-danger/10"
|
||||
>
|
||||
<Icon name="mdi:trash-can" class="wr:size-4" /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<div class="wr:flex wr:items-center wr:gap-3">
|
||||
<button
|
||||
id="start-tour"
|
||||
class="wr:h-9 wr:px-3 wr:rounded-md wr:bg-primary wr:text-primary-foreground wr:text-sm"
|
||||
>Start tour</button
|
||||
>
|
||||
|
||||
<Tooltip
|
||||
openOn="click"
|
||||
placement="bottom"
|
||||
offset={10}
|
||||
showArrow
|
||||
initialOpen
|
||||
>
|
||||
<button
|
||||
slot="trigger"
|
||||
id="tour-target"
|
||||
class="wr:h-9 wr:px-3 wr:rounded-md wr:border wr:border-border wr:bg-background wr:text-sm"
|
||||
>
|
||||
Create Project
|
||||
</button>
|
||||
|
||||
<div class="wr:w-72 wr:p-3 wr:space-y-2">
|
||||
<div class="wr:flex wr:items-center wr:justify-between">
|
||||
<span class="wr:text-sm wr:font-medium"
|
||||
>Create your first project</span
|
||||
>
|
||||
<span class="wr:text-[11px] wr:text-muted-foreground"
|
||||
>1 / 3</span
|
||||
>
|
||||
</div>
|
||||
<p class="wr:text-xs wr:text-muted-foreground">
|
||||
Projects group your tasks, docs and members. You can
|
||||
rename anytime.
|
||||
</p>
|
||||
<div class="wr:flex wr:justify-end wr:gap-2">
|
||||
<button
|
||||
class="wr:h-8 wr:px-3 wr:text-xs wr:rounded-md wr:border wr:border-border hover:wr:bg-muted/60"
|
||||
>Skip</button
|
||||
>
|
||||
<button
|
||||
class="wr:h-8 wr:px-3 wr:text-xs wr:rounded-md wr:bg-primary wr:text-primary-foreground hover:wr:bg-primary/90"
|
||||
>Next</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentLayout>
|
||||
Reference in New Issue
Block a user