chore: upgrade to react 19 beta and next 14 canary

This commit is contained in:
2024-05-19 14:53:00 +02:00
parent fe429295ef
commit 63e0be09e6
65 changed files with 2626 additions and 1898 deletions

View File

@@ -1,14 +1,9 @@
import { memo } from "react";
import equal from "react-fast-compare";
import * as icons from "@/components/icons";
import { Base, BaseProps } from "./base";
export type ClearProps = Omit<BaseProps, "icon" | "labelText">;
function RawClear({ iconOnly, ...props }: ClearProps) {
export function Clear({ iconOnly, ...props }: ClearProps) {
return <Base {...props} icon={<icons.X size={16} />} {...{ iconOnly }} labelText="Clear" />;
}
export const Clear = memo(RawClear, equal);

View File

@@ -1,6 +1,3 @@
import { memo, useCallback } from "react";
import equal from "react-fast-compare";
import * as icons from "@/components/icons";
import { Base, BaseProps } from "./base";
@@ -9,19 +6,17 @@ export type CopyProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
text: string;
};
function RawButton({ text, iconOnly, ...props }: CopyProps) {
const onClick: BaseProps["onClick"] = useCallback(() => {
export function Copy({ text, iconOnly, ...props }: CopyProps) {
const onClick: BaseProps["onClick"] = () => {
navigator.clipboard.writeText(text).catch(e => {
if (e instanceof Error) {
// eslint-disable-next-line no-alert
alert(e.message);
}
});
}, [text]);
};
return (
<Base {...props} icon={<icons.Copy size={16} />} {...{ iconOnly, onClick }} labelText="Copy" />
);
}
export const Copy = memo(RawButton, equal);

View File

@@ -1,5 +1,4 @@
import { memo, useCallback, useRef } from "react";
import equal from "react-fast-compare";
import { useRef } from "react";
import * as icons from "@/components/icons";
@@ -13,42 +12,39 @@ export type FileProps = Pick<InputProps, "accept"> &
onFileRead: (text: string) => void;
};
export function RawFile({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...props }: FileProps) {
export function File({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...props }: FileProps) {
const ref = useRef<HTMLInputElement>(null);
const onClick = () => ref.current?.click();
const onChange: NonNullable<InputProps["onChange"]> = useCallback(
e => {
const file = Array.from(e.currentTarget.files ?? []).at(0);
const onChange: NonNullable<InputProps["onChange"]> = e => {
const file = Array.from(e.currentTarget.files ?? []).at(0);
if (!file) {
return;
if (!file) {
return;
}
// TODO: reject if the file is unsupported
if (file.size > maxFileSizeMb * 2 ** 20) {
// eslint-disable-next-line no-alert
return alert(`The file is too big. Up to ${maxFileSizeMb}MiB.`);
}
const reader = new FileReader();
reader.onload = ({ target }) => {
if (typeof target?.result === "string") {
onFileRead(target?.result);
}
};
// TODO: reject if the file is unsupported
reader.readAsText(file);
if (file.size > maxFileSizeMb * 2 ** 20) {
// eslint-disable-next-line no-alert
return alert(`The file is too big. Up to ${maxFileSizeMb}MiB.`);
}
const reader = new FileReader();
reader.onload = ({ target }) => {
if (typeof target?.result === "string") {
onFileRead(target?.result);
}
};
reader.readAsText(file);
// clear selected file to accept the same file again
// eslint-disable-next-line no-param-reassign
e.currentTarget.value = "";
},
[maxFileSizeMb, onFileRead]
);
// clear selected file to accept the same file again
// eslint-disable-next-line no-param-reassign
e.currentTarget.value = "";
};
return (
<>
@@ -62,5 +58,3 @@ export function RawFile({ accept, iconOnly, maxFileSizeMb = 20, onFileRead, ...p
</>
);
}
export const File = memo(RawFile, equal);

View File

@@ -1,6 +1,3 @@
import { memo, useCallback } from "react";
import equal from "react-fast-compare";
import * as icons from "@/components/icons";
import { Base, BaseProps } from "./base";
@@ -9,8 +6,8 @@ export type PasteProps = Omit<BaseProps, "icon" | "labelText" | "onClick"> & {
onClipboardRead: (text: string) => void;
};
export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
const onClick: BaseProps["onClick"] = useCallback(() => {
export function Paste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
const onClick: BaseProps["onClick"] = () => {
navigator.clipboard
.readText()
.then(onClipboardRead)
@@ -20,7 +17,7 @@ export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
alert(e.message);
}
});
}, [onClipboardRead]);
};
return (
<Base
@@ -31,5 +28,3 @@ export function RawPaste({ iconOnly, onClipboardRead, ...props }: PasteProps) {
/>
);
}
export const Paste = memo(RawPaste, equal);

View File

@@ -1,6 +1,3 @@
import { memo } from "react";
import equal from "react-fast-compare";
import * as Icon from "@/components/icons";
import { Base, BaseProps } from "./base";
@@ -9,7 +6,7 @@ export type ToggleFullSizeProps = Omit<BaseProps, "icon" | "labelText"> & {
expanded: boolean;
};
function RawToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
export function ToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
return (
<Base
{...props}
@@ -18,5 +15,3 @@ function RawToggleFullSize({ expanded, ...props }: ToggleFullSizeProps) {
/>
);
}
export const ToggleFullSize = memo(RawToggleFullSize, equal);

View File

@@ -3,19 +3,17 @@
import React, { PropsWithChildren } from "react";
import { cn } from "@/lib/style";
import { SidebarStatus, useSidebarStatus } from "@/contexts/sidebar";
import { useSidebar } from "@/hooks/use-sidebar";
export function ClientLayout({ children }: PropsWithChildren) {
const sidebarStatus = useSidebarStatus();
const isOpen = sidebarStatus === SidebarStatus.Open;
const isClosed = sidebarStatus === SidebarStatus.Closed;
const { isSidebarOpen, isLoading } = useSidebar();
const isOpen = isSidebarOpen && !isLoading;
return (
<div
className={cn(
"grid h-full grid-rows-[3.5rem_1fr] transition-all",
isOpen && "grid-cols-[18rem_1fr]",
isClosed && "grid-cols-[0_1fr]"
isOpen ? "grid-cols-[18rem_1fr]" : "grid-cols-[0_1fr]"
)}
>
{children}

View File

@@ -1,6 +1,3 @@
import { memo } from "react";
import equal from "react-fast-compare";
type Props = {
icon: React.ReactNode;
title: string;
@@ -8,7 +5,7 @@ type Props = {
control: React.ReactNode;
};
function RawConfiguration({ icon, title, description, control }: Props) {
export function Configuration({ icon, title, description, control }: Props) {
return (
<div className="flex h-16 items-center gap-6 rounded border bg-configuration px-4">
{icon}
@@ -24,5 +21,3 @@ function RawConfiguration({ icon, title, description, control }: Props) {
</div>
);
}
export const Configuration = memo(RawConfiguration, equal);

View File

@@ -1,11 +1,8 @@
import { memo } from "react";
import equal from "react-fast-compare";
type Props = {
list: React.ReactNode[];
};
function RawConfigurations({ list }: Props) {
export function Configurations({ list }: Props) {
return (
<ul className="flex flex-col gap-1.5">
{list.map((config, i) => (
@@ -16,5 +13,3 @@ function RawConfigurations({ list }: Props) {
</ul>
);
}
export const Configurations = memo(RawConfigurations, equal);

View File

@@ -1,11 +1,8 @@
import { memo } from "react";
import equal from "react-fast-compare";
type Props = {
list: React.ReactNode[];
};
function RawControlMenu({ list }: Props) {
export function ControlMenu({ list }: Props) {
return (
<menu className="flex gap-2">
{list.map((control, i) => (
@@ -16,5 +13,3 @@ function RawControlMenu({ list }: Props) {
</menu>
);
}
export const ControlMenu = memo(RawControlMenu, equal);

View File

@@ -1,44 +1,42 @@
import { memo, MemoExoticComponent } from "react";
import * as icons from "lucide-react";
import equal from "react-fast-compare";
export type Icon = icons.LucideIcon | MemoExoticComponent<icons.LucideIcon>;
export const AlignLeft = memo(icons.AlignLeft, equal);
export const ArrowRightLeft = memo(icons.ArrowRightLeft, equal);
export const Binary = memo(icons.Binary, equal);
export const Braces = memo(icons.Braces, equal);
export const CaseSensitive = memo(icons.CaseSensitive, equal);
export const Check = memo(icons.Check, equal);
export const ChevronDown = memo(icons.ChevronDown, equal);
export const Clipboard = memo(icons.Clipboard, equal);
export const Code = memo(icons.Code2, equal);
export const Copy = memo(icons.Copy, equal);
export const Diff = memo(icons.Diff, equal);
export const Equal = memo(icons.Equal, equal);
export const File = memo(icons.FileIcon, equal);
export const Fingerprint = memo(icons.Fingerprint, equal);
export const GripHorizontal = memo(icons.GripHorizontal, equal);
export const GripVertical = memo(icons.GripVertical, equal);
export const Hash = memo(icons.Hash, equal);
export const Home = memo(icons.Home, equal);
export const Key = memo(icons.Key, equal);
export const Link = memo(icons.Link2, equal);
export const Maximize = memo(icons.Maximize2, equal);
export const Minimize = memo(icons.Minimize2, equal);
export const Menu = memo(icons.Menu, equal);
export const PackagePlus = memo(icons.PackagePlus, equal);
export const Paintbrush = memo(icons.Paintbrush2, equal);
export const Rows = memo(icons.Rows, equal);
export const Search = memo(icons.Search, equal);
export const Settings = memo(icons.Settings, equal);
export const Settings2 = memo(icons.Settings2, equal);
export const Space = memo(icons.Space, equal);
export const Sun = memo(icons.SunMedium, equal);
export const Type = memo(icons.Type, equal);
export const Minus = memo(icons.Minus, equal);
export const Moon = memo(icons.Moon, equal);
export const X = memo(icons.X, equal);
export type Icon = icons.LucideIcon;
export const {
AlignLeft,
ArrowRightLeft,
Binary,
Braces,
CaseSensitive,
ChevronDown,
Clipboard,
Copy,
Diff,
Equal,
Fingerprint,
GripHorizontal,
GripVertical,
Hash,
Home,
Key,
Menu,
PackagePlus,
Rows,
Search,
Settings,
Settings2,
Space,
Type,
Minus,
Moon,
X,
} = icons;
export const File = icons.FileIcon;
export const Code = icons.Code2;
export const Link = icons.Link2;
export const Maximize = icons.Maximize2;
export const Minimize = icons.Minimize2;
export const Paintbrush = icons.Paintbrush2;
export const Sun = icons.SunMedium;
export const GitHub = (props: icons.LucideProps) => (
<svg viewBox="0 0 438.549 438.549" {...props}>
<path

View File

@@ -1,7 +1,3 @@
import { memo } from "react";
function RawIndicator() {
export function Indicator() {
return <span className="inline-block h-[18px] w-[3px] rounded bg-indicator" />;
}
export const Indicator = memo(RawIndicator);

View File

@@ -1,4 +1,4 @@
import { ComponentPropsWithoutRef, useMemo } from "react";
import { ComponentPropsWithoutRef } from "react";
import { PanelResizeHandle as PanelResizeHandlePrimitive } from "react-resizable-panels";
import { cn } from "@/lib/style";
@@ -12,19 +12,15 @@ export const PanelResizeHandle = ({ direction = "vertical", className, ...props
const isVertical = direction === "vertical";
const isHorizontal = direction === "horizontal";
const classNames = useMemo(
() =>
cn(
isVertical && "w-4",
isHorizontal && "h-4",
"flex items-center justify-center",
"data-[resize-handle-state=drag]:bg-neutral-200",
"dark:data-[resize-handle-state=drag]:bg-neutral-600",
"data-[resize-handle-state=hover]:bg-neutral-300",
"dark:data-[resize-handle-state=hover]:bg-neutral-700",
className
),
[isVertical, isHorizontal, className]
const classNames = cn(
isVertical && "w-4",
isHorizontal && "h-4",
"flex items-center justify-center",
"data-[resize-handle-state=drag]:bg-neutral-200",
"dark:data-[resize-handle-state=drag]:bg-neutral-600",
"data-[resize-handle-state=hover]:bg-neutral-300",
"dark:data-[resize-handle-state=hover]:bg-neutral-700",
className
);
return (

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useRef } from "react";
import { useRef } from "react";
import { usePathname } from "next/navigation";
import * as Accordion from "@radix-ui/react-accordion";
@@ -18,7 +18,7 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
const pathname = usePathname();
const triggerRef = useRef<HTMLButtonElement>(null);
const onClick = useCallback(() => triggerRef.current?.click(), []);
const onClick = () => triggerRef.current?.click();
return (
<Accordion.AccordionItem value={href}>
@@ -40,13 +40,13 @@ export function ToolGroup({ Icon, title, href, tools, isOpend }: Props) {
<Accordion.Trigger
ref={triggerRef}
className={cn(
"absolute right-0 flex h-10 w-10 items-center justify-center rounded transition-all duration-0",
"absolute right-0 flex size-10 items-center justify-center rounded transition-all duration-0",
"hover:bg-accent",
"[&[data-state=open]>svg]:rotate-180"
)}
aria-label="toggle open/close state of the tool group"
>
<icons.ChevronDown className="h-4 w-4 transition-transform duration-200" />
<icons.ChevronDown className="size-4 transition-transform duration-200" />
</Accordion.Trigger>
</div>
</Accordion.Header>

View File

@@ -1,6 +1,5 @@
"use client";
import { memo } from "react";
import Link, { LinkProps } from "next/link";
import { Tool } from "@/config/tools";
@@ -14,8 +13,9 @@ type Props = Pick<Tool, "Icon" | "shortTitle"> &
};
// FIXME: css outline messed up
function RawToolLink({ Icon, shortTitle: title, href, onClick, highlight, grouped }: Props) {
export function ToolLink({ Icon, shortTitle: title, href, onClick, highlight, grouped }: Props) {
return (
// @ts-expect-error react 19 beta error
<Link
className={cn(
"flex h-10 items-center gap-3 whitespace-nowrap rounded",
@@ -35,5 +35,3 @@ function RawToolLink({ Icon, shortTitle: title, href, onClick, highlight, groupe
</Link>
);
}
export const ToolLink = memo(RawToolLink);

View File

@@ -1,27 +1,20 @@
"use client";
import { useCallback } from "react";
import Link from "next/link";
import { siteConfig } from "@/config/site";
import { cn } from "@/lib/style";
import { useSidebar } from "@/hooks/use-sidebar";
import * as icons from "@/components/icons";
import { Menu } from "@/components/icons";
import { ThemeToggle } from "@/components/theme-toggle";
import { SidebarStatus, useSetSidebarStatus, useSidebarStatus } from "@/contexts/sidebar";
type Props = {
className?: string;
};
export function SiteHeader({ className }: Props) {
const setSidebarStatus = useSetSidebarStatus();
const sidebarStatus = useSidebarStatus();
const handleMenuToggle = useCallback(() => {
setSidebarStatus(
sidebarStatus === SidebarStatus.Open ? SidebarStatus.Closed : SidebarStatus.Open
);
}, [sidebarStatus, setSidebarStatus]);
const { toggleSidebar } = useSidebar();
return (
<header className={cn("flex items-center justify-between px-4", className)}>
@@ -29,17 +22,18 @@ export function SiteHeader({ className }: Props) {
<button
type="button"
className="flex items-center rounded p-1.5 hover:bg-accent"
onClick={handleMenuToggle}
onClick={toggleSidebar}
>
<Menu />
</button>
{/* @ts-expect-error react 19 beta error */}
<Link className="text-lg" href="/">
{siteConfig.name}
</Link>
</div>
<div className="flex gap-x-1">
<a
className="group h-10 w-10 rounded-md p-2"
className="group size-10 rounded-md p-2"
href={siteConfig.links.github}
target="_blank"
rel="noreferrer"

View File

@@ -2,7 +2,7 @@ export function TailwindIndicator() {
if (process.env.NODE_ENV === "production") return null;
return (
<div className="fixed bottom-1 left-1 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="fixed bottom-1 left-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>

View File

@@ -15,8 +15,8 @@ export function ThemeToggle() {
size="taller"
onClick={() => setTheme(resolvedTheme === "light" ? "dark" : "light")}
>
<icons.Sun className="h-7 w-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<icons.Moon className="absolute h-7 w-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<icons.Sun className="size-7 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<icons.Moon className="absolute size-7 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
);

View File

@@ -6,6 +6,7 @@ export type ToolCardProps = Pick<Tool, "Icon" | "longTitle" | "description" | "h
export function ToolCard({ Icon, longTitle, description, href }: ToolCardProps) {
return (
// @ts-expect-error react 19 beta error
<Link className="rounded" {...{ href }}>
<div className="group flex h-80 w-44 flex-col items-center gap-5 overflow-hidden rounded border bg-card p-5 text-card-foreground hover:bg-card-hover">
<div className="flex flex-col p-5">

View File

@@ -1,4 +1,4 @@
import * as React from "react";
import { ComponentProps } from "react";
import { cva, VariantProps } from "class-variance-authority";
import { cn } from "@/lib/style";
@@ -25,17 +25,8 @@ export const buttonVariants = cva(
}
);
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>;
export type ButtonProps = ComponentProps<"button"> & VariantProps<typeof buttonVariants>;
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => (
<button
{...{ ref }}
className={cn(buttonVariants({ variant, size }), className)}
type="button"
{...props}
/>
)
export const Button = ({ className, variant, size, ...props }: ButtonProps) => (
<button className={cn(buttonVariants({ variant, size }), className)} type="button" {...props} />
);
Button.displayName = "Button";

View File

@@ -1,30 +1,31 @@
"use client";
import { ComponentPropsWithoutRef, forwardRef } from "react";
import { ComponentProps } from "react";
import { DiffEditor as MonacoDiffEditor } from "@monaco-editor/react";
import { useTheme } from "next-themes";
export type EditorProps = ComponentPropsWithoutRef<typeof MonacoDiffEditor>;
// @ts-expect-error react 19 beta error
export type DiffEditorProps = ComponentProps<typeof MonacoDiffEditor>;
// @ts-expect-error react 19 beta error
export const DiffEditor = ({ options, theme, ...props }: DiffEditorProps) => {
const { theme: appTheme } = useTheme();
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
export const DiffEditor = forwardRef<HTMLTextAreaElement, EditorProps>(
({ options, theme, ...props }, ref) => {
const { theme: appTheme } = useTheme();
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
return (
<MonacoDiffEditor
{...{ ref }}
theme={themeToUse}
options={{
tabFocusMode: true,
automaticLayout: true,
scrollBeyondLastLine: false,
...options, // NOTE: merge shallowly
}}
{...props}
/>
);
}
);
DiffEditor.displayName = "Editor";
return (
// @ts-expect-error react 19 beta error
<MonacoDiffEditor
// @ts-expect-error react 19 beta error
theme={themeToUse}
// @ts-expect-error react 19 beta error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
options={{
tabFocusMode: true,
automaticLayout: true,
scrollBeyondLastLine: false,
// @ts-expect-error react 19 beta error
...options, // NOTE: merge shallowly
}}
{...props}
/>
);
};

View File

@@ -1,10 +1,11 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import MonacoEditor from "@monaco-editor/react";
import { useTheme } from "next-themes";
export type EditorProps = React.ComponentPropsWithoutRef<typeof MonacoEditor>;
// @ts-expect-error react 19 beta error
export type EditorProps = ComponentProps<typeof MonacoEditor>;
/**
* NOTE: This component maybe doesn't shrink according to the container component's width
@@ -12,26 +13,28 @@ export type EditorProps = React.ComponentPropsWithoutRef<typeof MonacoEditor>;
* @see https://github.com/suren-atoyan/monaco-react/issues/346
*
*/
export const Editor = React.forwardRef<HTMLTextAreaElement, EditorProps>(
({ options, theme, ...props }, ref) => {
const { theme: appTheme } = useTheme();
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
// @ts-expect-error react 19 beta error
export const Editor = ({ options, theme, ...props }: EditorProps) => {
const { theme: appTheme } = useTheme();
const themeToUse = theme ?? (appTheme === "light" ? "light" : "vs-dark");
return (
<MonacoEditor
{...{ ref }}
theme={themeToUse}
options={{
tabFocusMode: true,
detectIndentation: false,
minimap: { enabled: false },
automaticLayout: true,
scrollBeyondLastLine: false,
...options, // NOTE: merge shallowly
}}
{...props}
/>
);
}
);
Editor.displayName = "Editor";
return (
// @ts-expect-error react 19 beta error
<MonacoEditor
// @ts-expect-error react 19 beta error
theme={themeToUse}
// @ts-expect-error react 19 beta error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
options={{
tabFocusMode: true,
detectIndentation: false,
minimap: { enabled: false },
automaticLayout: true,
scrollBeyondLastLine: false,
// @ts-expect-error react 19 beta error
...options, // NOTE: merge shallowly
}}
{...props}
/>
);
};

View File

@@ -1,30 +1,23 @@
import * as React from "react";
import equal from "react-fast-compare";
import { ComponentProps } from "react";
import { cn } from "@/lib/style";
export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
export type InputProps = ComponentProps<"input"> & {
fontMono?: true;
};
const RawInput = React.forwardRef<HTMLInputElement, InputProps>(
({ className, fontMono, ...props }, ref) => (
<input
{...{ ref }}
className={cn(
"border-b-1 h-9 rounded border border-b-muted-foreground bg-input px-3 py-2 outline-none",
"placeholder:text-muted-foreground",
"hover:bg-input-hover",
"focus:border-b-2 focus:border-b-indicator focus:bg-input-focus focus:pb-[7px]",
"disabled:cursor-not-allowed disabled:opacity-50",
fontMono && "font-mono",
className
)}
spellCheck="false"
{...props}
/>
)
export const Input = ({ className, fontMono, ...props }: InputProps) => (
<input
className={cn(
"border-b-1 h-9 rounded border border-b-muted-foreground bg-input px-3 py-2 outline-none",
"placeholder:text-muted-foreground",
"hover:bg-input-hover",
"focus:border-b-2 focus:border-b-indicator focus:bg-input-focus focus:pb-[7px]",
"disabled:cursor-not-allowed disabled:opacity-50",
fontMono && "font-mono",
className
)}
spellCheck="false"
{...props}
/>
);
RawInput.displayName = "RawInput";
export const Input = React.memo(RawInput, equal);

View File

@@ -1,16 +1,12 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/style";
export const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => (
export const Label = ({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) => (
<LabelPrimitive.Root
{...{ ref }}
className={cn(
"leading-none",
"peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
@@ -18,5 +14,4 @@ export const Label = React.forwardRef<
)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
);

View File

@@ -0,0 +1,23 @@
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";
import { SidebarProvider } from "@/hooks/use-sidebar";
import { TailwindIndicator } from "@/components/tailwind-indicator";
import { SearchTextProvider } from "@/contexts/search-text";
import { ClientLayout } from "../client-layout";
export function Providers({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider {...props}>
<SearchTextProvider>
<SidebarProvider>
<ClientLayout>{children}</ClientLayout>
<TailwindIndicator />
</SidebarProvider>
</SearchTextProvider>
</NextThemesProvider>
);
}

View File

@@ -1,22 +1,22 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "@/lib/style";
import * as icons from "@/components/icons";
import { Indicator } from "@/components/indicator";
export type Props = React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>;
export type Props = ComponentProps<typeof SelectPrimitive.Root>;
export const { Root, Group, Value } = SelectPrimitive;
export const Trigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
export const Trigger = ({
className,
children,
...props
}: ComponentProps<typeof SelectPrimitive.Trigger>) => (
<SelectPrimitive.Trigger
{...{ ref }}
className={cn(
"flex h-9 items-center justify-between rounded-md border bg-select px-2.5 py-1.5",
"placeholder:text-muted-foreground",
@@ -28,19 +28,20 @@ export const Trigger = React.forwardRef<
>
{children}
<SelectPrimitive.Icon asChild>
<icons.ChevronDown className="h-4 w-4 opacity-50" />
<icons.ChevronDown className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
Trigger.displayName = SelectPrimitive.Trigger.displayName;
);
export const Content = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
export const Content = ({
className,
children,
position = "popper",
...props
}: ComponentProps<typeof SelectPrimitive.Content>) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
{...{ ref, position }}
position={position}
className={cn(
"relative z-50 overflow-hidden rounded-md border bg-select-content text-select-content-foreground shadow-md animate-in fade-in-80",
position === "popper" && "translate-y-1",
@@ -59,27 +60,18 @@ export const Content = React.forwardRef<
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
Content.displayName = SelectPrimitive.Content.displayName;
);
export const Label = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
{...{ ref }}
className={cn("py-1.5 pl-8 pr-2 font-semibold", className)}
{...props}
/>
));
Label.displayName = SelectPrimitive.Label.displayName;
export const Label = ({ className, ...props }: ComponentProps<typeof SelectPrimitive.Label>) => (
<SelectPrimitive.Label className={cn("py-1.5 pl-8 pr-2 font-semibold", className)} {...props} />
);
export const Item = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
export const Item = ({
className,
children,
...props
}: ComponentProps<typeof SelectPrimitive.Item>) => (
<SelectPrimitive.Item
{...{ ref }}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm px-2.5 py-1.5 outline-none",
"hover:bg-select-item-hover",
@@ -96,17 +88,11 @@ export const Item = React.forwardRef<
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
Item.displayName = SelectPrimitive.Item.displayName;
);
export const Separator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
{...{ ref }}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
Separator.displayName = SelectPrimitive.Separator.displayName;
export const Separator = ({
className,
...props
}: ComponentProps<typeof SelectPrimitive.Separator>) => (
<SelectPrimitive.Separator className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
);

View File

@@ -1,22 +1,23 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/style";
export const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
export const Separator = ({
className,
orientation = "horizontal",
decorative = true,
...props
}: ComponentProps<typeof SeparatorPrimitive.Root>) => (
<SeparatorPrimitive.Root
{...{ ref, decorative, orientation }}
className={cn(
"bg-separator",
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
className
)}
{...{ decorative, orientation }}
{...props}
/>
));
Separator.displayName = SeparatorPrimitive.Root.displayName;
);

View File

@@ -1,38 +1,34 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/style";
export type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>;
export type SwitchProps = ComponentProps<typeof SwitchPrimitives.Root>;
export const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.Root>, SwitchProps>(
({ className, ...props }, ref) => (
<SwitchPrimitives.Root
{...{ ref }}
export const Switch = ({ className, ...props }: SwitchProps) => (
<SwitchPrimitives.Root
className={cn(
"group inline-flex h-5 w-10 shrink-0 cursor-pointer items-center rounded-full border border-muted-foreground bg-switch",
"hover:bg-switch-hover",
"disabled:cursor-not-allowed disabled:opacity-50",
"hover:disabled:bg-switch",
"data-[state=checked]:border-transparent data-[state=checked]:bg-indicator",
"data-[state=checked]:hover:bg-indicator-hover",
"data-[state=checked]:disabled:hover:bg-indicator",
className
)}
{...props}
>
<SwitchPrimitives.Thumb
className={cn(
"group inline-flex h-5 w-10 shrink-0 cursor-pointer items-center rounded-full border border-muted-foreground bg-switch",
"hover:bg-switch-hover",
"disabled:cursor-not-allowed disabled:opacity-50",
"hover:disabled:bg-switch",
"data-[state=checked]:border-transparent data-[state=checked]:bg-indicator",
"data-[state=checked]:hover:bg-indicator-hover",
"data-[state=checked]:disabled:hover:bg-indicator",
className
"pointer-events-none block size-3.5 rounded-full bg-foreground/80 shadow-lg transition-transform",
"group-hover:size-4",
"group-disabled:size-3.5",
"data-[state=checked]:translate-x-[22px] data-[state=checked]:bg-background",
"data-[state=unchecked]:translate-x-0.5"
)}
{...props}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-3.5 w-3.5 rounded-full bg-foreground/80 shadow-lg transition-transform",
"group-hover:h-4 group-hover:w-4",
"group-disabled:h-3.5 group-disabled:w-3.5",
"data-[state=checked]:translate-x-[22px] data-[state=checked]:bg-background",
"data-[state=unchecked]:translate-x-0.5"
)}
/>
</SwitchPrimitives.Root>
)
/>
</SwitchPrimitives.Root>
);
Switch.displayName = SwitchPrimitives.Root.displayName;

View File

@@ -1,27 +1,20 @@
import * as React from "react";
import equal from "react-fast-compare";
import { ComponentProps } from "react";
import { cn } from "@/lib/style";
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
export type TextareaProps = ComponentProps<"textarea">;
export const RawTextarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => (
<textarea
{...{ ref }}
className={cn(
"border-b-1 resize-none rounded border border-b-muted-foreground bg-textarea px-3 py-2 font-mono outline-none",
"placeholder:text-muted-foreground",
"hover:bg-textarea-hover",
"focus:border-b-2 focus:border-b-indicator focus:bg-textarea-focus focus:pb-[7px]",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
spellCheck="false"
{...props}
/>
)
export const Textarea = ({ className, ...props }: TextareaProps) => (
<textarea
className={cn(
"border-b-1 resize-none rounded border border-b-muted-foreground bg-textarea px-3 py-2 font-mono outline-none",
"placeholder:text-muted-foreground",
"hover:bg-textarea-hover",
"focus:border-b-2 focus:border-b-indicator focus:bg-textarea-focus focus:pb-[7px]",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
spellCheck="false"
{...props}
/>
);
RawTextarea.displayName = "RawTextarea";
export const Textarea = React.memo(RawTextarea, equal);

View File

@@ -1,29 +1,22 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import { cn } from "@/lib/style";
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>
>(({ className, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center gap-2.5", className)}
{...props}
/>
));
const ToggleGroup = ({ className, ...props }: ComponentProps<typeof ToggleGroupPrimitive.Root>) => (
<ToggleGroupPrimitive.Root className={cn("flex items-center gap-2.5", className)} {...props} />
);
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item>
>(({ className, children, ...props }, ref) => (
const ToggleGroupItem = ({
className,
children,
...props
}: ComponentProps<typeof ToggleGroupPrimitive.Item>) => (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
"focus-visible:ring-ring inline-flex h-10 items-center justify-center rounded-md bg-accent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-sky-600 data-[state=on]:text-white",
className
@@ -32,8 +25,6 @@ const ToggleGroupItem = React.forwardRef<
>
{children}
</ToggleGroupPrimitive.Item>
));
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
);
export { ToggleGroup, ToggleGroupItem };

View File

@@ -1,6 +1,6 @@
"use client";
import * as React from "react";
import { ComponentProps } from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/style";
@@ -9,12 +9,13 @@ export const TooltipProvider = TooltipPrimitive.Provider;
export const Tooltip = TooltipPrimitive.Root;
export const TooltipTrigger = TooltipPrimitive.Trigger;
export const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
export const TooltipContent = ({
className,
sideOffset = 4,
...props
}: ComponentProps<typeof TooltipPrimitive.Content>) => (
<TooltipPrimitive.Content
{...{ ref, sideOffset }}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-tooltip px-3 py-1.5 text-tooltip-foreground shadow-md animate-in fade-in-50",
"data-[side=bottom]:slide-in-from-top-1",
@@ -25,5 +26,4 @@ export const TooltipContent = React.forwardRef<
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
);