refactor: switch to using Bun (#718)
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { LockKeyholeOpenIcon, StarIcon } from "lucide-react";
|
||||
import { getColorFromText, isLightColor } from "@app/core/utils/color";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipArrow,
|
||||
@@ -7,15 +6,10 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@components/UI/Tooltip.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { LockKeyholeOpenIcon, StarIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type RGBColor = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
};
|
||||
|
||||
interface AvatarProps {
|
||||
text: string | number;
|
||||
size?: "sm" | "lg";
|
||||
@@ -24,49 +18,6 @@ interface AvatarProps {
|
||||
showFavorite?: boolean;
|
||||
}
|
||||
|
||||
class ColorUtils {
|
||||
static hexToRgb(hex: number): RGBColor {
|
||||
return {
|
||||
r: (hex & 0xff0000) >> 16,
|
||||
g: (hex & 0x00ff00) >> 8,
|
||||
b: hex & 0x0000ff,
|
||||
a: 255,
|
||||
};
|
||||
}
|
||||
|
||||
static rgbToHex(color: RGBColor): number {
|
||||
return (
|
||||
(Math.round(color.a) << 24) |
|
||||
(Math.round(color.r) << 16) |
|
||||
(Math.round(color.g) << 8) |
|
||||
Math.round(color.b)
|
||||
);
|
||||
}
|
||||
|
||||
static isLight(color: RGBColor): boolean {
|
||||
const brightness = (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
|
||||
return brightness > 127.5;
|
||||
}
|
||||
}
|
||||
|
||||
const getColorFromText = (text: string): RGBColor => {
|
||||
if (!text) {
|
||||
return { r: 0, g: 0, b: 0, a: 255 };
|
||||
}
|
||||
let hash = 0;
|
||||
for (let i = 0; i < text?.length; i++) {
|
||||
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
||||
hash |= 0;
|
||||
}
|
||||
|
||||
return {
|
||||
r: (hash & 0xff0000) >> 16,
|
||||
g: (hash & 0x00ff00) >> 8,
|
||||
b: hash & 0x0000ff,
|
||||
a: 255,
|
||||
};
|
||||
};
|
||||
|
||||
export const Avatar = ({
|
||||
text,
|
||||
size = "sm",
|
||||
@@ -83,7 +34,7 @@ export const Avatar = ({
|
||||
|
||||
const safeText = text?.toString().toUpperCase();
|
||||
const bgColor = getColorFromText(safeText);
|
||||
const isLight = ColorUtils.isLight(bgColor);
|
||||
const isLight = isLightColor(bgColor);
|
||||
const textColor = isLight ? "#000000" : "#FFFFFF";
|
||||
const initials = safeText?.slice(0, 4) ?? t("unknown.shortName");
|
||||
|
||||
@@ -100,48 +51,42 @@ export const Avatar = ({
|
||||
color: textColor,
|
||||
}}
|
||||
>
|
||||
{showFavorite
|
||||
? (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<StarIcon
|
||||
className="absolute -top-0.5 -right-0.5 z-10 size-4 stroke-1 fill-yellow-400"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
color: `rgb(${bgColor.r}, ${bgColor.g}, ${bgColor.b})`,
|
||||
}}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
|
||||
{t("nodeDetail.favorite.label", { ns: "nodes" })}
|
||||
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
: null}
|
||||
{showError
|
||||
? (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<LockKeyholeOpenIcon
|
||||
className="absolute -bottom-0.5 -right-0.5 z-10 size-4 text-red-500 stroke-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
|
||||
{t("nodeDetail.error.label", { ns: "nodes" })}
|
||||
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
: null}
|
||||
<p className="p-1 text-nowrap">
|
||||
{initials}
|
||||
</p>
|
||||
{showFavorite ? (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<StarIcon
|
||||
className="absolute -top-0.5 -right-0.5 z-10 size-4 stroke-1 fill-yellow-400"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
color: `rgb(${bgColor.r}, ${bgColor.g}, ${bgColor.b})`,
|
||||
}}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
|
||||
{t("nodeDetail.favorite.label", { ns: "nodes" })}
|
||||
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : null}
|
||||
{showError ? (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<LockKeyholeOpenIcon
|
||||
className="absolute -bottom-0.5 -right-0.5 z-10 size-4 text-red-500 stroke-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
|
||||
{t("nodeDetail.error.label", { ns: "nodes" })}
|
||||
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : null}
|
||||
<p className="p-1 text-nowrap">{initials}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import type * as React from "react";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:cursor-not-allowed dark:focus:ring-offset-slate-900 cursor-pointer",
|
||||
@@ -19,8 +19,7 @@ const buttonVariants = cva(
|
||||
"bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-500 dark:text-white dark:hover:bg-slate-400",
|
||||
ghost:
|
||||
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
|
||||
link:
|
||||
"bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
|
||||
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 py-2 px-4",
|
||||
@@ -39,8 +38,7 @@ const buttonVariants = cva(
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||
|
||||
export interface ButtonProps
|
||||
extends
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
icon?: React.ReactNode;
|
||||
iconAlignment?: "left" | "right";
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { Checkbox } from "@components/UI/Checkbox/index.tsx";
|
||||
import React from "react";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import type React from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@components/UI/Label.tsx", () => ({
|
||||
Label: (
|
||||
{ children, className, htmlFor, id }: {
|
||||
children: React.ReactNode;
|
||||
className: string;
|
||||
htmlFor: string;
|
||||
id: string;
|
||||
},
|
||||
) => (
|
||||
Label: ({
|
||||
children,
|
||||
className,
|
||||
htmlFor,
|
||||
id,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className: string;
|
||||
htmlFor: string;
|
||||
id: string;
|
||||
}) => (
|
||||
<label
|
||||
data-testid="label-component"
|
||||
className={className}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useId, useState } from "react";
|
||||
import { Check } from "lucide-react";
|
||||
import { Label } from "@components/UI/Label.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { Check } from "lucide-react";
|
||||
import { useId } from "react";
|
||||
|
||||
interface CheckboxProps {
|
||||
checked?: boolean;
|
||||
@@ -16,10 +15,9 @@ interface CheckboxProps {
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
checked,
|
||||
checked = false,
|
||||
onChange,
|
||||
className,
|
||||
labelClassName,
|
||||
id: propId,
|
||||
children,
|
||||
disabled = false,
|
||||
@@ -30,71 +28,49 @@ export function Checkbox({
|
||||
const generatedId = useId();
|
||||
const id = propId || generatedId;
|
||||
|
||||
const [isChecked, setIsChecked] = useState(checked || false);
|
||||
|
||||
// Make sure setIsChecked state updates with checked
|
||||
useEffect(() => {
|
||||
setIsChecked(checked || false);
|
||||
}, [checked]);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (disabled) return;
|
||||
|
||||
const newChecked = !isChecked;
|
||||
setIsChecked(newChecked);
|
||||
onChange?.(newChecked);
|
||||
const handleToggle = (): void => {
|
||||
if (!disabled) {
|
||||
onChange?.(!checked);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center", className)}>
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
checked={isChecked}
|
||||
onChange={handleToggle}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
name={name}
|
||||
className="sr-only"
|
||||
{...rest}
|
||||
/>
|
||||
<div
|
||||
onClick={handleToggle}
|
||||
role="presentation"
|
||||
className={cn(
|
||||
"w-6 h-6 border-2 border-gray-500 rounded-md flex items-center justify-center",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
||||
isChecked ? "" : "",
|
||||
)}
|
||||
>
|
||||
{isChecked && (
|
||||
<div className="animate-fade-in scale-100 opacity-100">
|
||||
<Check className="w-4 h-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{children && (
|
||||
<div className="ml-3 text-sm">
|
||||
<Label
|
||||
htmlFor={id}
|
||||
id={`${id}-label`}
|
||||
className={cn(
|
||||
"text-gray-900 dark:text-gray-900",
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
||||
labelClassName,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Label>
|
||||
<label
|
||||
className={cn(
|
||||
"inline-flex items-center gap-3",
|
||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={handleToggle}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
name={name}
|
||||
className="sr-only peer"
|
||||
{...rest}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 border-gray-500 transition-colors",
|
||||
"peer-focus-visible:ring-2 peer-focus-visible:ring-blue-500 peer-focus-visible:ring-offset-2",
|
||||
{ "border-slate-500 bg-slate-500": checked },
|
||||
)}
|
||||
>
|
||||
{checked && (
|
||||
<div className="animate-fade-in">
|
||||
<Check className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{children && (
|
||||
<span className="text-sm text-gray-900 dark:text-gray-100">
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Dialog, DialogContent } from "@components/UI/Dialog.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import type { DialogProps } from "@radix-ui/react-dialog";
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
import { Search } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { Dialog, DialogContent } from "@components/UI/Dialog.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import newGithubIssueUrl from "@core/utils/github.ts";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { Heading } from "@components/UI/Typography/Heading.tsx";
|
||||
import { Link } from "@components/UI/Typography/Link.tsx";
|
||||
import { P } from "@components/UI/Typography/P.tsx";
|
||||
import newGithubIssueUrl from "@core/utils/github.ts";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
export function ErrorPage({ error }: { error: Error }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<article className="w-full h-screen overflow-y-auto bg-background-primary text-text-primary">
|
||||
<section className="flex shrink md:flex-row gap-16 mt-20 px-4 md:px-8 text-lg md:text-xl space-y-2 place-items-center dark:bg-background-primary text-slate-900 dark:text-text-primary">
|
||||
@@ -19,12 +19,8 @@ export function ErrorPage({ error }: { error: Error }) {
|
||||
<Heading as="h2" className="text-text-primary">
|
||||
{t("errorPage.title")}
|
||||
</Heading>
|
||||
<P>
|
||||
{t("errorPage.description1")}
|
||||
</P>
|
||||
<P>
|
||||
{t("errorPage.description2")}
|
||||
</P>
|
||||
<P>{t("errorPage.description1")}</P>
|
||||
<P>{t("errorPage.description2")}</P>
|
||||
<P>Please include the following information in your report:</P>
|
||||
<ul className="list-disc list-inside text-sm">
|
||||
<li>{t("errorPage.reportSteps.step1")}</li>
|
||||
|
||||
@@ -27,12 +27,14 @@ const Footer = ({ className, ...props }: FooterProps) => {
|
||||
<Trans
|
||||
i18nKey="footer.text"
|
||||
components={[
|
||||
// biome-ignore lint/a11y/useAnchorContent: because I want to use the link component
|
||||
<a
|
||||
key="vercel"
|
||||
rel="noopener noreferrer"
|
||||
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
|
||||
className="hover:underline text-link"
|
||||
/>,
|
||||
// biome-ignore lint/a11y/useAnchorContent: because I want to use the link component
|
||||
<a
|
||||
key="legal"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Button, type ButtonVariant } from "@components/UI/Button.tsx";
|
||||
import { Input } from "@components/UI/Input.tsx";
|
||||
import {
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@components/UI/Select.tsx";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface ActionButton {
|
||||
@@ -33,58 +33,62 @@ export interface GeneratorProps extends React.BaseHTMLAttributes<HTMLElement> {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Generator = (
|
||||
{
|
||||
type,
|
||||
devicePSKBitCount,
|
||||
id = "pskInput",
|
||||
variant,
|
||||
value,
|
||||
actionButtons,
|
||||
bits,
|
||||
selectChange,
|
||||
inputChange,
|
||||
disabled,
|
||||
showPasswordToggle,
|
||||
showCopyButton,
|
||||
...props
|
||||
}: GeneratorProps,
|
||||
) => {
|
||||
const Generator = ({
|
||||
type,
|
||||
devicePSKBitCount,
|
||||
id = "pskInput",
|
||||
variant,
|
||||
value,
|
||||
actionButtons,
|
||||
bits,
|
||||
selectChange,
|
||||
inputChange,
|
||||
disabled,
|
||||
showPasswordToggle,
|
||||
showCopyButton,
|
||||
...props
|
||||
}: GeneratorProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const passwordRequiredBitSize = bits ? bits : [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
{
|
||||
text: t("security.128bit"),
|
||||
value: "16",
|
||||
key: "bit128",
|
||||
},
|
||||
{
|
||||
text: t("security.8bit"),
|
||||
value: "1",
|
||||
key: "bit8",
|
||||
},
|
||||
{
|
||||
text: t("security.0bit"),
|
||||
value: "0",
|
||||
key: "bit0",
|
||||
},
|
||||
];
|
||||
const passwordRequiredBitSize = bits
|
||||
? bits
|
||||
: [
|
||||
{
|
||||
text: t("security.256bit"),
|
||||
value: "32",
|
||||
key: "bit256",
|
||||
},
|
||||
{
|
||||
text: t("security.128bit"),
|
||||
value: "16",
|
||||
key: "bit128",
|
||||
},
|
||||
{
|
||||
text: t("security.8bit"),
|
||||
value: "1",
|
||||
key: "bit8",
|
||||
},
|
||||
{
|
||||
text: t("security.0bit"),
|
||||
value: "0",
|
||||
key: "bit0",
|
||||
},
|
||||
];
|
||||
|
||||
// Invokes onChange event on the input element when the value changes from the parent component
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) return;
|
||||
if (!inputRef.current) {
|
||||
return;
|
||||
}
|
||||
const setValue = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
"value",
|
||||
)?.set;
|
||||
|
||||
if (!setValue) return;
|
||||
if (!setValue) {
|
||||
return;
|
||||
}
|
||||
inputRef.current.value = "";
|
||||
setValue.call(inputRef.current, value);
|
||||
inputRef.current.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { useCopyToClipboard } from "@core/hooks/useCopyToClipboard.ts";
|
||||
import { usePasswordVisibilityToggle } from "@core/hooks/usePasswordVisibilityToggle.ts";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Check, Copy, Eye, EyeOff, type LucideIcon, X } from "lucide-react";
|
||||
import { useCopyToClipboard } from "@core/hooks/useCopyToClipboard.ts";
|
||||
import { usePasswordVisibilityToggle } from "@core/hooks/usePasswordVisibilityToggle.ts";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const cnInvalidBase = "border-2 border-red-500 dark:border-red-500";
|
||||
@@ -35,8 +35,10 @@ type InputActionType = {
|
||||
};
|
||||
|
||||
export interface InputProps
|
||||
extends
|
||||
Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "suffix">,
|
||||
extends Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
"prefix" | "suffix"
|
||||
>,
|
||||
VariantProps<typeof inputVariants> {
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
@@ -127,7 +129,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
const actions = potentialActions.filter((action) => action.condition);
|
||||
|
||||
const inputType = showPasswordToggle
|
||||
? (isVisible ? "text" : "password")
|
||||
? isVisible
|
||||
? "text"
|
||||
: "password"
|
||||
: type;
|
||||
|
||||
const hasPrefix = !!prefix;
|
||||
@@ -197,7 +201,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
className={cn(
|
||||
"inline-flex h-full items-center justify-center px-2.5 text-slate-500 hover:bg-slate-100 hover:text-slate-700 focus:outline-none focus:ring-1 focus:ring-slate-400 focus:ring-offset-0 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-200 dark:focus:ring-slate-500 last:hover:rounded-r-md last:dark:hover:rounded-r-md",
|
||||
disabled && "text-slate-300 dark:text-slate-600",
|
||||
action.id === "copy-value" && isCopied &&
|
||||
action.id === "copy-value" &&
|
||||
isCopied &&
|
||||
"text-green-600 dark:text-green-500",
|
||||
)}
|
||||
onClick={action.onClick}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as MenubarPrimitive from "@radix-ui/react-menubar";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const MenubarMenu = MenubarPrimitive.Menu;
|
||||
|
||||
const MenubarGroup = MenubarPrimitive.Group;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cn } from "../../core/utils/cn.ts";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
import { cn } from "../../core/utils/cn.ts";
|
||||
|
||||
interface MultiSelectProps {
|
||||
children: React.ReactNode;
|
||||
@@ -8,8 +8,9 @@ interface MultiSelectProps {
|
||||
}
|
||||
|
||||
const MultiSelect = ({ children, className = "" }: MultiSelectProps) => {
|
||||
return <div className={cn("flex flex-wrap gap-2", className)}>{children}
|
||||
</div>;
|
||||
return (
|
||||
<div className={cn("flex flex-wrap gap-2", className)}>{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface MultiSelectItemProps {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Button } from "@components/UI/Button.tsx";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { useSidebar } from "@core/stores/sidebarStore.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
|
||||
export interface SidebarButtonProps {
|
||||
label: string;
|
||||
@@ -44,10 +44,7 @@ export const SidebarButton = ({
|
||||
disabled={disabled}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon
|
||||
size={isButtonCollapsed ? 20 : 18}
|
||||
className="flex-shrink-0"
|
||||
/>
|
||||
<Icon size={isButtonCollapsed ? 20 : 18} className="flex-shrink-0" />
|
||||
)}
|
||||
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { Heading } from "@components/UI/Typography/Heading.tsx";
|
||||
import { useSidebar } from "@core/stores/sidebarStore.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import type React from "react";
|
||||
|
||||
interface SidebarSectionProps {
|
||||
label: string;
|
||||
@@ -16,13 +16,7 @@ export const SidebarSection = ({
|
||||
}: SidebarSectionProps) => {
|
||||
const { isCollapsed } = useSidebar();
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"py-2",
|
||||
isCollapsed ? "px-0" : "px-4",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className={cn("py-2", isCollapsed ? "px-0" : "px-4", className)}>
|
||||
<Heading
|
||||
as="h3"
|
||||
className={cn(
|
||||
@@ -38,9 +32,7 @@ export const SidebarSection = ({
|
||||
{label}
|
||||
</Heading>
|
||||
|
||||
<div className="space-y-1">
|
||||
{children}
|
||||
</div>
|
||||
<div className="space-y-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useId, useState } from "react";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import { useId, useState } from "react";
|
||||
|
||||
export interface SliderProps {
|
||||
value: number[];
|
||||
@@ -9,6 +9,7 @@ export interface SliderProps {
|
||||
max: number;
|
||||
onValueChange?: (value: number[]) => void;
|
||||
onValueCommit?: (value: number[]) => void;
|
||||
id?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
trackClassName?: string;
|
||||
@@ -21,6 +22,7 @@ export function Slider({
|
||||
step = 1,
|
||||
min = 0,
|
||||
max,
|
||||
id,
|
||||
onValueChange,
|
||||
onValueCommit,
|
||||
disabled = false,
|
||||
@@ -28,15 +30,19 @@ export function Slider({
|
||||
trackClassName,
|
||||
rangeClassName,
|
||||
thumbClassName,
|
||||
|
||||
...props
|
||||
}: SliderProps) {
|
||||
const [internalValue, setInternalValue] = useState<number[]>(value);
|
||||
const isControlled = value !== undefined;
|
||||
const currentValue = isControlled ? value! : internalValue;
|
||||
const id = useId();
|
||||
const currentValue = isControlled ? value : internalValue;
|
||||
const generatedId = useId();
|
||||
const internalId = id ? id : generatedId;
|
||||
|
||||
const handleValueChange = (newValue: number[]) => {
|
||||
if (!isControlled) setInternalValue(newValue);
|
||||
if (!isControlled) {
|
||||
setInternalValue(newValue);
|
||||
}
|
||||
onValueChange?.(newValue);
|
||||
};
|
||||
|
||||
@@ -50,6 +56,7 @@ export function Slider({
|
||||
"relative flex items-center select-none touch-none",
|
||||
className,
|
||||
)}
|
||||
id={internalId}
|
||||
value={currentValue}
|
||||
step={step}
|
||||
min={min}
|
||||
@@ -72,14 +79,14 @@ export function Slider({
|
||||
)}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{currentValue.map((_, i) => (
|
||||
{currentValue.map((_) => (
|
||||
<SliderPrimitive.Thumb
|
||||
key={`${id}-thumb-${i}`}
|
||||
key={`${internalId}-thumb`}
|
||||
className={cn(
|
||||
"block w-4 h-4 rounded-full bg-white border border-slate-400 shadow-md",
|
||||
thumbClassName,
|
||||
)}
|
||||
aria-label={`Thumb ${i + 1}`}
|
||||
aria-label={`Thumb ${internalId}`}
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
|
||||
@@ -13,7 +13,6 @@ const sizeClasses = {
|
||||
export function Spinner({ className, size = "md", ...props }: SpinnerProps) {
|
||||
return (
|
||||
<div
|
||||
aria-label="Loading..."
|
||||
className={cn(
|
||||
"flex items-center justify-center fade-in-50 fade-out-50",
|
||||
className,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
|
||||
@@ -41,8 +41,8 @@ const toastVariants = cva(
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
& React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root>
|
||||
& VariantProps<typeof toastVariants>
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import * as React from "react";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const toggleGroupItemClasses = [
|
||||
"flex flex-1 h-10 items-center justify-center first:rounded-l last:rounded-r ",
|
||||
@@ -34,10 +34,7 @@ const ToggleGroupItem = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
...toggleGroupItemClasses,
|
||||
className,
|
||||
)}
|
||||
className={cn(...toggleGroupItemClasses, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = ({ ...props }) => <TooltipPrimitive.Root {...props} />;
|
||||
|
||||
@@ -2,8 +2,7 @@ import type React from "react";
|
||||
|
||||
const headingStyles = {
|
||||
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
|
||||
h2:
|
||||
"scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700",
|
||||
h2: "scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700",
|
||||
h3: "scroll-m-20 text-lg font-semibold tracking-tight",
|
||||
h4: "scroll-m-20 text-xl font-semibold tracking-tight",
|
||||
h5: "scroll-m-20 text-lg font-medium tracking-tight",
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import {
|
||||
Link as RouterLink,
|
||||
LinkProps as RouterLinkProps,
|
||||
type LinkProps as RouterLinkProps,
|
||||
} from "@tanstack/react-router";
|
||||
|
||||
export interface LinkProps extends RouterLinkProps {
|
||||
href: string;
|
||||
children?:
|
||||
| React.ReactNode
|
||||
| ((
|
||||
state: { isActive: boolean; isTransitioning: boolean },
|
||||
) => React.ReactNode);
|
||||
| ((state: {
|
||||
isActive: boolean;
|
||||
isTransitioning: boolean;
|
||||
}) => React.ReactNode);
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user