refactor: switch to using Bun (#718)

This commit is contained in:
Dan Ditomaso
2025-07-16 22:30:10 -04:00
committed by GitHub
parent acfd7ebd2d
commit 704d06cfe7
401 changed files with 35294 additions and 39107 deletions
+40 -95
View File
@@ -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>
);
};
+4 -6
View File
@@ -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>
);
}
+2 -3
View File
@@ -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 -2
View File
@@ -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;
+6 -10
View File
@@ -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"
+46 -42
View File
@@ -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 }));
+12 -7
View File
@@ -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 -2
View File
@@ -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 -2
View File
@@ -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 -2
View File
@@ -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 -2
View File
@@ -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 -2
View File
@@ -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>
);
};
+15 -8
View File
@@ -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 -2
View File
@@ -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 -2
View File
@@ -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<
+2 -2
View File
@@ -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 -2
View File
@@ -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;
}