Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { Fragment, MouseEventHandler, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { ChevronDownIcon } from "~/components/ui/icons/vendor";
|
||||
|
||||
interface Props {
|
||||
right?: boolean;
|
||||
button?: ReactNode;
|
||||
options?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
btnClassName?: string;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
disabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export default function Dropdown({ button, options, right, onClick, className, btnClassName, disabled, isLoading }: Props) {
|
||||
return (
|
||||
<Menu as="div" className={clsx(className, "relative inline-block text-left")}>
|
||||
<div>
|
||||
<Menu.Button
|
||||
disabled={disabled || isLoading}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
isLoading && "base-spinner cursor-not-allowed",
|
||||
btnClassName ?? "inline-flex w-full items-center justify-center rounded-md px-3 py-2 text-sm font-medium shadow-2xs",
|
||||
disabled || isLoading
|
||||
? "bg-surface-neutral-secondary/90 cursor-not-allowed"
|
||||
: clsx("bg-surface-neutral-primary", "shadow-focus focus:outline-hidden focus:ring-offset-2")
|
||||
)}
|
||||
>
|
||||
{button}
|
||||
{!btnClassName && <ChevronDownIcon className="text-text-neutral-tertiary -mr-1 ml-2 h-4 w-4" />}
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={clsx(
|
||||
"bg-surface-neutral-modal border-border-default absolute z-40 mt-2 w-48 overflow-hidden rounded-md border shadow-lg ring-1 ring-border-default focus:outline-hidden",
|
||||
right ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
)}
|
||||
>
|
||||
<div className="py-1">{options}</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// @vitest-environment node
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "DropdownDots.tsx"), "utf8");
|
||||
|
||||
describe("DropdownDots disabled links", () => {
|
||||
test("disabled href items do not render navigable links", () => {
|
||||
expect(source).toContain("item.href && !item.disabled");
|
||||
expect(source).not.toContain('to={item.disabled ? "" : item.href}');
|
||||
expect(source).toContain("aria-disabled={item.disabled || undefined}");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { MoreVerticalIcon } from "~/components/ui/icons/vendor";
|
||||
import { Button } from "../button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../dropdown-menu";
|
||||
import { Link } from "react-router";
|
||||
import { Fragment } from "react/jsx-runtime";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
items: {
|
||||
name: "separator" | React.ReactNode | (string & {});
|
||||
href?: string;
|
||||
target?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
hidden?: boolean;
|
||||
}[];
|
||||
size?: "xs" | "sm" | "lg";
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
classMenuContent?: string;
|
||||
variant?: "default" | "ghost" | "outline";
|
||||
ariaLabel?: string;
|
||||
}
|
||||
export default function DropdownDots({ title, items, size = "sm", children, className, classMenuContent, variant = "ghost", ariaLabel }: Props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Button type="button" variant={variant} size={size} className={className} aria-label={ariaLabel ?? (children ? undefined : t("shared.openMenu"))}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<MoreVerticalIcon
|
||||
className="opacity-90"
|
||||
size={cn(
|
||||
size === "xs" && 12,
|
||||
size === "sm" && 16,
|
||||
size === "lg" && 20
|
||||
//
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className={cn("max-w-44", classMenuContent)}>
|
||||
{title && (
|
||||
<Fragment>
|
||||
<DropdownMenuLabel>{title}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
</Fragment>
|
||||
)}
|
||||
<DropdownMenuGroup>
|
||||
{items
|
||||
.filter((item) => !item.hidden)
|
||||
.map((item, idx) => (
|
||||
<Fragment key={idx}>
|
||||
{item.name === "separator" ? (
|
||||
<DropdownMenuSeparator />
|
||||
) : (
|
||||
<DropdownMenuItem key={idx} asChild>
|
||||
{item.href && !item.disabled ? (
|
||||
<Link
|
||||
to={item.href}
|
||||
className={item.className}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
target={item.target}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={cn("w-full", item.disabled && "cursor-not-allowed opacity-50", item.className)}
|
||||
disabled={item.disabled}
|
||||
aria-disabled={item.disabled || undefined}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</button>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
{/* <DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-red-600" onClick={() => deleteItem(item.id)}>
|
||||
Delete
|
||||
</DropdownMenuItem> */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Fragment, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import OptionsIcon from "../icons/OptionsIcon";
|
||||
|
||||
interface Props {
|
||||
right?: boolean;
|
||||
options?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
button?: ReactNode;
|
||||
buttonDataUiId?: string;
|
||||
disabled?: boolean;
|
||||
width?: "w-48" | "w-56" | "w-64" | "w-72" | "w-80" | "w-96";
|
||||
}
|
||||
|
||||
export default function DropdownOptions({ options, right, className, button, buttonDataUiId, disabled, width = "w-48" }: Props) {
|
||||
return (
|
||||
<Menu as="div" className={clsx(className, "relative inline-block text-left")}>
|
||||
<div>
|
||||
<Menu.Button data-ui-id={buttonDataUiId} disabled={disabled}>
|
||||
{button ?? <OptionsIcon />}
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={clsx(
|
||||
"bg-surface-neutral-modal absolute z-40 mt-2 rounded-md shadow-lg ring-1 ring-border-default focus:outline-hidden",
|
||||
right ? "left-0 origin-top-left" : "right-0 origin-top-right",
|
||||
width
|
||||
)}
|
||||
>
|
||||
<div className="py-1">{options}</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Fragment, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import OptionsIcon from "../icons/OptionsIcon";
|
||||
import { Link } from "react-router";
|
||||
|
||||
interface Props {
|
||||
items?: (
|
||||
| {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
href: string;
|
||||
target?: "_blank" | string;
|
||||
}
|
||||
)[];
|
||||
button?: ReactNode;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
width?: "w-48" | "w-56" | "w-64" | "w-72" | "w-80" | "w-96";
|
||||
right?: boolean;
|
||||
}
|
||||
|
||||
export default function DropdownSimple({ items, button, disabled, className, right, width = "w-48" }: Props) {
|
||||
return (
|
||||
<Menu as="div" className={clsx(className, "relative inline-block text-left")}>
|
||||
<div>
|
||||
<Menu.Button disabled={disabled}>{button ?? <OptionsIcon />}</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={clsx(
|
||||
"bg-surface-neutral-modal absolute z-40 mt-2 rounded-md shadow-lg ring-1 ring-border-default focus:outline-hidden",
|
||||
right ? "left-0 origin-top-left" : "right-0 origin-top-right",
|
||||
width
|
||||
)}
|
||||
>
|
||||
<div className="py-1">
|
||||
{items?.map((item, i) => {
|
||||
return (
|
||||
<Menu.Item key={i}>
|
||||
{({ active, close }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{"href" in item ? (
|
||||
<Link
|
||||
to={item.href}
|
||||
target={item.target}
|
||||
className={clsx("w-full text-left", active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary", "block px-4 py-2 text-sm")}
|
||||
onClick={() => close()}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
item.onClick();
|
||||
close();
|
||||
}}
|
||||
className={clsx("w-full text-left", active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Fragment, MouseEventHandler, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { Link } from "react-router";
|
||||
import { ChevronDownIcon } from "~/components/ui/icons/vendor";
|
||||
|
||||
interface Props {
|
||||
right?: boolean;
|
||||
title: string;
|
||||
options: {
|
||||
title: string;
|
||||
routePath?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
btnClassName?: string;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export default function DropdownStyless({ title, options, right, onClick, className, btnClassName }: Props) {
|
||||
return (
|
||||
<Menu as="div" className={clsx(className, "relative inline-block text-left")}>
|
||||
<div>
|
||||
<Menu.Button
|
||||
onClick={onClick}
|
||||
className={
|
||||
btnClassName ??
|
||||
"hover:bg-surface-neutral-secondary shadow-focus bg-surface-neutral-primary text-text-neutral-secondary inline-flex w-full justify-center rounded-md text-sm font-medium focus:outline-hidden focus:ring-offset-2 focus:ring-offset-gray-100"
|
||||
}
|
||||
>
|
||||
{title}
|
||||
{!btnClassName && <ChevronDownIcon className="text-text-neutral-tertiary -mr-1 ml-2 h-5 w-5" />}
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={clsx(
|
||||
"bg-surface-neutral-modal absolute z-40 mt-2 w-48 rounded-md shadow-lg ring-1 ring-border-default focus:outline-hidden",
|
||||
right ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
)}
|
||||
>
|
||||
<div className="py-1">
|
||||
{options.map((item, idx) => {
|
||||
return (
|
||||
<Menu.Item key={idx}>
|
||||
{({ active, close }) => (
|
||||
<>
|
||||
{item.onClick && (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
"hover:bg-surface-neutral-secondary w-full text-left",
|
||||
item.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
disabled={item.disabled}
|
||||
onClick={() => {
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</button>
|
||||
)}
|
||||
{item.routePath && (
|
||||
<Link
|
||||
to={item.routePath}
|
||||
className={clsx("w-full text-left", active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Fragment, MouseEventHandler, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { Link } from "react-router";
|
||||
import { ChevronDownIcon } from "~/components/ui/icons/vendor";
|
||||
|
||||
interface Props {
|
||||
right?: boolean;
|
||||
button?: ReactNode;
|
||||
to?: string;
|
||||
options?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function DropdownWithClick({ button, to, options, right, onClick, className, disabled }: Props) {
|
||||
return (
|
||||
<span className={clsx(className, "relative z-10 inline-flex rounded-md shadow-2xs")}>
|
||||
{to ? (
|
||||
<Link
|
||||
to={to}
|
||||
className={clsx(
|
||||
"focus:border-brand-brand-border-solid-default shadow-focus border-border-default bg-surface-neutral-primary text-text-neutral-secondary relative -mr-1 inline-flex items-center rounded-l-md border px-4 py-2 text-sm font-medium focus:z-10 focus:outline-hidden",
|
||||
disabled ? "cursor-not-allowed opacity-80" : "hover:bg-surface-neutral-secondary focus:bg-surface-neutral-secondary"
|
||||
)}
|
||||
>
|
||||
{button}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
type="button"
|
||||
className={clsx(
|
||||
"focus:border-brand-brand-border-solid-default shadow-focus border-border-default bg-surface-neutral-primary text-text-neutral-secondary relative -mr-1 inline-flex items-center rounded-l-md border px-4 py-2 text-sm font-medium focus:z-10 focus:outline-hidden",
|
||||
disabled ? "cursor-not-allowed opacity-80" : "hover:bg-surface-neutral-secondary focus:bg-surface-neutral-secondary"
|
||||
)}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
)}
|
||||
<Menu as="span" className="relative -ml-px block">
|
||||
<Menu.Button className="hover:bg-surface-neutral-secondary focus:border-brand-brand-border-solid-default shadow-focus border-border-default text-text-neutral-tertiary bg-surface-neutral-primary relative inline-flex items-center rounded-r-md border px-2 py-2 text-sm font-medium focus:z-10 focus:outline-hidden">
|
||||
<ChevronDownIcon className="h-5 w-5" />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={clsx(
|
||||
"bg-surface-neutral-modal absolute z-40 mt-2 w-48 rounded-md shadow-lg ring-1 ring-border-default focus:outline-hidden",
|
||||
right ? "left-0 origin-top-left" : "right-0 origin-top-right"
|
||||
)}
|
||||
>
|
||||
<div className="py-1">{options}</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import React, { MouseEventHandler, ReactNode } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { usePopper } from "./use-popper";
|
||||
import { Link } from "react-router";
|
||||
|
||||
interface Props {
|
||||
button: ReactNode;
|
||||
options: {
|
||||
title?: string;
|
||||
routePath?: string;
|
||||
onClick?: () => void | undefined;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
render?: (active: boolean, close: () => void) => ReactNode;
|
||||
}[];
|
||||
size?: "sm" | "md" | "lg" | "xl";
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
}
|
||||
export default function MenuWithPopper({ button, options, size = "sm", onClick, className }: Props) {
|
||||
let [trigger, container] = usePopper({
|
||||
strategy: "fixed",
|
||||
});
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<span>
|
||||
<Menu.Button ref={trigger} onClick={onClick} className={className}>
|
||||
{button}
|
||||
</Menu.Button>
|
||||
</span>
|
||||
|
||||
<div ref={container} className={clsx(size === "sm" && "w-56", size === "md" && "w-64", size === "lg" && "w-80", size === "xl" && "w-96")}>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transform scale-95 opacity-0"
|
||||
enterTo="transform scale-100 opacity-100"
|
||||
leave="transition duration-75 ease-out"
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Menu.Items className="border-border-default bg-surface-neutral-modal divide-border-default divide-y rounded-md border shadow-lg outline-hidden">
|
||||
<div className="py-1">
|
||||
{options.map((item, idx) => {
|
||||
return (
|
||||
<Menu.Item key={idx}>
|
||||
{({ active, close }) => (
|
||||
<>
|
||||
{item.render && item.render(active, close)}
|
||||
{item.onClick && (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
item.className,
|
||||
"hover:bg-surface-neutral-secondary w-full text-left",
|
||||
item.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
disabled={item.disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</button>
|
||||
)}
|
||||
{item.routePath && (
|
||||
<Link
|
||||
to={item.routePath}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
close();
|
||||
}}
|
||||
className={clsx(
|
||||
item.className,
|
||||
"hover:bg-surface-neutral-secondary w-full text-left",
|
||||
item.disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
active ? "text-text-neutral-primary bg-surface-neutral-secondary" : "text-text-neutral-secondary",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import PreviewDropdownsSimple from "./PreviewDropdownsSimple";
|
||||
import { t as i18nT } from "i18next";
|
||||
import PreviewDropdownsWithClick from "./PreviewDropdownsWithClick";
|
||||
|
||||
export default function PreviewDropdowns() {
|
||||
return (
|
||||
<div id="dropdowns">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-medium">{i18nT("components.dropdowns.preview.simple")}</h3>
|
||||
<div className="border-border bg-background space-x-2 border border-dashed p-6">
|
||||
<PreviewDropdownsSimple />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-medium">{i18nT("components.dropdowns.preview.title")}</h3>
|
||||
<div className="border-border bg-background space-x-2 border border-dashed p-6">
|
||||
<PreviewDropdownsWithClick />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Link, useLocation } from "react-router";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import { t as i18nT } from "i18next";
|
||||
import clsx from "clsx";
|
||||
import Dropdown from "./Dropdown";
|
||||
|
||||
export default function PreviewDropdownsSimple() {
|
||||
const currentRoute = useLocation().pathname;
|
||||
return (
|
||||
<div className="not-prose border-border bg-background space-x-2 border border-dashed p-6">
|
||||
<div className="w-full space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<Dropdown
|
||||
right={true}
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.dropdownClick"))}
|
||||
button={<div>{i18nT("components.dropdowns.preview.dropdown")}</div>}
|
||||
options={
|
||||
<div>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.clicked"))}
|
||||
className={clsx("w-full text-left", active ? "text-foreground bg-secondary/90" : "text-foreground/80", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
{i18nT("components.dropdowns.preview.button")}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<Link
|
||||
to={currentRoute}
|
||||
className={clsx("w-full", active ? "text-foreground bg-secondary/90" : "text-foreground/80", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.link")}</div>
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
}
|
||||
></Dropdown>
|
||||
<Dropdown
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.dropdownClick"))}
|
||||
button={<div>{i18nT("components.dropdowns.preview.dropdown")}</div>}
|
||||
options={
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="hover:bg-secondary text-foreground/80 block w-full px-4 py-2 text-left text-sm focus:outline-hidden"
|
||||
tabIndex={-1}
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.clicked"))}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.button")}</div>
|
||||
</button>
|
||||
<Link
|
||||
to={currentRoute}
|
||||
className="hover:bg-secondary text-foreground/80 block w-full px-4 py-2 text-left text-sm focus:outline-hidden"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.link")}</div>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
></Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Link, useLocation } from "react-router";
|
||||
import { t as i18nT } from "i18next";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import DropdownWithClick from "./DropdownWithClick";
|
||||
|
||||
export default function PreviewDropdownsWithClick() {
|
||||
const currentRoute = useLocation().pathname;
|
||||
return (
|
||||
<div className="not-prose border-border bg-background space-x-2 border border-dashed p-6">
|
||||
<div className="w-full space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<DropdownWithClick
|
||||
right={true}
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.dropdownClick"))}
|
||||
button={<div>{i18nT("components.dropdowns.preview.withClick")}</div>}
|
||||
options={
|
||||
<div>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.clicked"))}
|
||||
className={clsx("w-full text-left", active ? "text-foreground bg-secondary/90" : "text-foreground/80", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
{i18nT("components.dropdowns.preview.button")}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<Link
|
||||
to={currentRoute}
|
||||
className={clsx("w-full", active ? "text-foreground bg-secondary/90" : "text-foreground/80", "block px-4 py-2 text-sm")}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.link")}</div>
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<DropdownWithClick
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.dropdownClick"))}
|
||||
button={<div>{i18nT("components.dropdowns.preview.withClick")}</div>}
|
||||
options={
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="hover:bg-secondary text-foreground/80 block w-full px-4 py-2 text-left text-sm focus:outline-hidden"
|
||||
tabIndex={-1}
|
||||
onClick={() => alert(i18nT("components.dropdowns.preview.clicked"))}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.button")}</div>
|
||||
</button>
|
||||
<Link
|
||||
to={currentRoute}
|
||||
className="hover:bg-secondary text-foreground/80 block w-full px-4 py-2 text-left text-sm focus:outline-hidden"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div>{i18nT("components.dropdowns.preview.link")}</div>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { RefCallback, useRef, useCallback, useMemo } from "react";
|
||||
import { createPopper, Options } from "@popperjs/core";
|
||||
|
||||
/**
|
||||
* Example implementation to use Popper: https://popper.js.org/
|
||||
*/
|
||||
export function usePopper(options?: Partial<Options>): [RefCallback<Element | null>, RefCallback<HTMLElement | null>] {
|
||||
let reference = useRef<Element>(null);
|
||||
let popper = useRef<HTMLElement>(null);
|
||||
|
||||
let cleanupCallback = useRef(() => {});
|
||||
|
||||
let instantiatePopper = useCallback(() => {
|
||||
if (!reference.current) return;
|
||||
if (!popper.current) return;
|
||||
|
||||
if (cleanupCallback.current) cleanupCallback.current();
|
||||
|
||||
cleanupCallback.current = createPopper(reference.current, popper.current, options).destroy;
|
||||
}, [reference, popper, cleanupCallback, options]);
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
(referenceDomNode) => {
|
||||
// @ts-ignore
|
||||
reference.current = referenceDomNode;
|
||||
instantiatePopper();
|
||||
},
|
||||
(popperDomNode) => {
|
||||
// @ts-ignore
|
||||
popper.current = popperDomNode;
|
||||
instantiatePopper();
|
||||
},
|
||||
],
|
||||
[reference, popper, instantiatePopper]
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user