Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
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]
);
}