feat: update linter rules and improve code structure

- Added overrides in biome.json to disable specific linter rules for UI components.
- Reorganized imports and improved code readability in various UI components.
- Refactored components to use fieldset instead of div for better semantics.
- Enhanced accessibility by updating roles and aria attributes in components.
- Fixed minor issues with key props in map functions for better performance.
- Updated focus styles and improved CSS for better visual feedback.
This commit is contained in:
asepharyana
2026-07-23 17:05:08 +07:00
parent 4dbaba7529
commit e528b6a726
53 changed files with 6537 additions and 236 deletions
+30 -29
View File
@@ -1,36 +1,37 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). # asepharyana-hub-hub
## Getting Started Personal portfolio SPA — backend/infrastructure projects and live monitoring dashboard.
First, run the development server: Built with **Next.js 16**, **shadcn/ui** (base-nova), **Tailwind CSS v4**, and `next-themes`.
## Stack
- **Framework:** Next.js 16 (Turbopack, React Compiler)
- **UI:** shadcn/ui v4 (base-nova style) + Tailwind CSS v4
- **Font:** Geist (sans) + Geist Mono via `next/font`
- **Theme:** `next-themes` — light/dark with amber "server LED" focus glow (dark mode)
- **Icons:** Lucide React
- **Linting:** Biome
## Pages
| Route | Description |
|-------|-------------|
| `/` | Portfolio landing page — hero, about, skills, featured project (ZeaVis Edu), infra CTA |
| `/dashboard` | Live infrastructure monitor — Docker services, Traefik metrics, Jaeger traces, Prometheus resources |
## Theme
Twilight Terminal — warm off-white (light) / deep indigo-slate (dark) with amber-gold primary in dark mode. All component styling is driven by CSS custom properties in `globals.css`.
## Dev
```bash ```bash
npm run dev bun dev # Start dev server
# or bun run lint # Biome lint
yarn dev bun run ci # Biome CI mode
# or
pnpm dev
# or
bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. ## Deploy
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. Deployed as a Docker container behind Traefik on VPS. See `infra/` in the monorepo root for compose files and Traefik config.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+17 -2
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -17,7 +17,7 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "preset": "recommended",
"suspicious": { "suspicious": {
"noUnknownAtRules": "off" "noUnknownAtRules": "off"
} }
@@ -27,6 +27,21 @@
"react": "recommended" "react": "recommended"
} }
}, },
"overrides": [
{
"includes": ["src/components/ui/**"],
"linter": {
"rules": {
"security": {
"noDangerouslySetInnerHtml": "off"
},
"suspicious": {
"noArrayIndexKey": "off"
}
}
}
}
],
"assist": { "assist": {
"actions": { "actions": {
"source": { "source": {
+24 -30
View File
File diff suppressed because one or more lines are too long
+6292
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -18,28 +18,28 @@
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^1.25.0", "lucide-react": "^1.26.0",
"next": "16.2.11", "next": "16.2.11",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.2.4", "react": "19.2.8",
"react-day-picker": "^10.0.1", "react-day-picker": "^10.0.1",
"react-dom": "19.2.4", "react-dom": "19.2.8",
"react-resizable-panels": "^4.12.2", "react-resizable-panels": "^4.12.2",
"recharts": "3.8.0", "recharts": "3.10.0",
"shadcn": "^4.14.0", "shadcn": "^4.14.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0" "tw-animate-css": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.2.0", "@biomejs/biome": "2.5.5",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^26",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5.9.3"
}, },
"ignoreScripts": [ "ignoreScripts": [
"sharp", "sharp",
+5 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import http from "node:http"; import http from "node:http";
import { NextResponse } from "next/server";
const JAEGER = "http://jaeger:16686"; const JAEGER = "http://jaeger:16686";
const PROMETHEUS = "http://prometheus:9090"; const PROMETHEUS = "http://prometheus:9090";
@@ -31,7 +31,9 @@ function dockerFetch(path: string): Promise<unknown> {
http http
.get({ socketPath: DOCKER_SOCK, path }, (res) => { .get({ socketPath: DOCKER_SOCK, path }, (res) => {
let data = ""; let data = "";
res.on("data", (c: string) => (data += c)); res.on("data", (c: string) => {
data += c;
});
res.on("end", () => { res.on("end", () => {
try { try {
resolve(JSON.parse(data)); resolve(JSON.parse(data));
@@ -129,7 +131,7 @@ async function promRange(query: string, steps = 20): Promise<number[]> {
if (!result?.[0]?.values) return []; if (!result?.[0]?.values) return [];
return result[0].values.map((v) => { return result[0].values.map((v) => {
const f = parseFloat(v[1] as string); const f = parseFloat(v[1] as string);
return isNaN(f) ? 0 : f; return Number.isNaN(f) ? 0 : f;
}); });
} }
+3 -1
View File
@@ -133,7 +133,9 @@
.dark *:focus-visible { .dark *:focus-visible {
@apply ring-[3px]; @apply ring-[3px];
outline: none; outline: none;
box-shadow: 0 0 8px oklch(0.7 0.15 75 / 0.3), 0 0 0 3px oklch(0.7 0.15 75 / 0.5); box-shadow:
0 0 8px oklch(0.7 0.15 75 / 0.3),
0 0 0 3px oklch(0.7 0.15 75 / 0.5);
} }
html:not(.dark) *:focus-visible { html:not(.dark) *:focus-visible {
@apply ring-[3px]; @apply ring-[3px];
+6 -1
View File
@@ -32,7 +32,12 @@ export default function RootLayout({
suppressHydrationWarning suppressHydrationWarning
> >
<body className="flex min-h-full flex-col bg-background text-foreground"> <body className="flex min-h-full flex-col bg-background text-foreground">
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> <ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<DashboardHeader /> <DashboardHeader />
{children} {children}
</ThemeProvider> </ThemeProvider>
+8 -8
View File
@@ -98,15 +98,15 @@ export default function Home() {
I&rsquo;m a Teknik Informatika student passionate about backend I&rsquo;m a Teknik Informatika student passionate about backend
engineering, distributed systems, and infrastructure. I spend my engineering, distributed systems, and infrastructure. I spend my
time building RESTful APIs, learning container orchestration with time building RESTful APIs, learning container orchestration with
Docker &amp; Traefik, and exploring observability with OpenTelemetry Docker &amp; Traefik, and exploring observability with
&amp; Jaeger. OpenTelemetry &amp; Jaeger.
</p> </p>
<p> <p>
I was the Back-End lead on the ZeaVis Edu capstone team (&ldquo;AI I was the Back-End lead on the ZeaVis Edu capstone team (&ldquo;AI
for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild), for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild), a
a 5-person team with task division across Back-End, Front-End, 5-person team with task division across Back-End, Front-End, and
and Machine Learning. I handled the system architecture, API Machine Learning. I handled the system architecture, API design,
design, RESTful API development, and Docker deployment on VPS. RESTful API development, and Docker deployment on VPS.
</p> </p>
</div> </div>
</div> </div>
@@ -260,8 +260,8 @@ export default function Home() {
</p> </p>
<p className="max-w-md text-sm text-muted-foreground"> <p className="max-w-md text-sm text-muted-foreground">
The dashboard tracks Docker services, Traefik request metrics, The dashboard tracks Docker services, Traefik request metrics,
Prometheus system resources, and Jaeger traces — all running on Prometheus system resources, and Jaeger traces — all running on my
my VPS deployment. VPS deployment.
</p> </p>
<a href="/dashboard" className={cn(buttonVariants())}> <a href="/dashboard" className={cn(buttonVariants())}>
Live Dashboard Live Dashboard
+1 -2
View File
@@ -1,7 +1,6 @@
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"; import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import { cn } from "@/lib/utils";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) { function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
return ( return (
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) { function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />; return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const attachmentVariants = cva( const attachmentVariants = cva(
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed", "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -5
View File
@@ -1,9 +1,8 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cn } from "@/lib/utils";
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"; import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) { function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
return ( return (
@@ -63,8 +62,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-page" data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page" aria-current="page"
className={cn("font-normal text-foreground", className)} className={cn("font-normal text-foreground", className)}
{...props} {...props}
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+4 -6
View File
@@ -1,12 +1,11 @@
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
const buttonGroupVariants = cva( const buttonGroupVariants = cva(
"flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", "flex w-fit items-stretch border-0 p-0 m-0 *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
{ {
variants: { variants: {
orientation: { orientation: {
@@ -26,10 +25,9 @@ function ButtonGroup({
className, className,
orientation, orientation,
...props ...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) { }: React.ComponentProps<"fieldset"> & VariantProps<typeof buttonGroupVariants>) {
return ( return (
<div <fieldset
role="group"
data-slot="button-group" data-slot="button-group"
data-orientation={orientation} data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)} className={cn(buttonGroupVariants({ orientation }), className)}
+10 -11
View File
@@ -1,20 +1,19 @@
"use client"; "use client";
import * as React from "react";
import {
DayPicker,
getDefaultClassNames,
type DayButton,
type Locale,
} from "react-day-picker";
import { cn } from "@/lib/utils";
import { Button, buttonVariants } from "@/components/ui/button";
import { import {
ChevronDownIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
ChevronDownIcon,
} from "lucide-react"; } from "lucide-react";
import * as React from "react";
import {
type DayButton,
DayPicker,
getDefaultClassNames,
type Locale,
} from "react-day-picker";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Calendar({ function Calendar({
className, className,
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+9 -11
View File
@@ -1,13 +1,12 @@
"use client"; "use client";
import * as React from "react";
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
} from "embla-carousel-react"; } from "embla-carousel-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type CarouselApi = UseEmblaCarouselType[1]; type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>; type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -118,16 +117,16 @@ function Carousel({
canScrollNext, canScrollNext,
}} }}
> >
<div {/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: <section> has implicit role="region" which supports aria-roledescription */}
<section
onKeyDownCapture={handleKeyDown} onKeyDownCapture={handleKeyDown}
className={cn("relative", className)} className={cn("relative", className)}
role="region"
aria-roledescription="carousel" aria-roledescription="carousel"
data-slot="carousel" data-slot="carousel"
{...props} {...props}
> >
{children} {children}
</div> </section>
</CarouselContext.Provider> </CarouselContext.Provider>
); );
} }
@@ -153,16 +152,15 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { function CarouselItem({ className, ...props }: React.ComponentProps<"fieldset">) {
const { orientation } = useCarousel(); const { orientation } = useCarousel();
return ( return (
<div <fieldset
role="group"
aria-roledescription="slide" aria-roledescription="slide"
data-slot="carousel-item" data-slot="carousel-item"
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full border-0 p-0 m-0",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
className, className,
)} )}
+11 -7
View File
@@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import * as RechartsPrimitive from "recharts";
import type { TooltipValueType } from "recharts"; import type { TooltipValueType } from "recharts";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -200,13 +200,17 @@ function ChartTooltipContent({
{payload {payload
.filter((item) => item.type !== "none") .filter((item) => item.type !== "none")
.map((item, index) => { .map((item, index) => {
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`; const itemKey = `${nameKey ?? item.name ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key); const itemConfig = getPayloadConfigFromPayload(
config,
item,
itemKey,
);
const indicatorColor = color ?? item.payload?.fill ?? item.color; const indicatorColor = color ?? item.payload?.fill ?? item.color;
return ( return (
<div <div
key={index} key={itemKey}
className={cn( className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center", indicator === "dot" && "items-center",
@@ -299,12 +303,12 @@ function ChartLegendContent({
{payload {payload
.filter((item) => item.type !== "none") .filter((item) => item.type !== "none")
.map((item, index) => { .map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`; const itemKey = `${nameKey ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key); const itemConfig = getPayloadConfigFromPayload(config, item, itemKey);
return ( return (
<div <div
key={index} key={itemKey}
className={cn( className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground", "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
)} )}
+1 -2
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"; import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) { function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
return ( return (
+3 -4
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import * as React from "react";
import { Combobox as ComboboxPrimitive } from "@base-ui/react"; import { Combobox as ComboboxPrimitive } from "@base-ui/react";
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import * as React from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
InputGroup, InputGroup,
@@ -11,7 +10,7 @@ import {
InputGroupButton, InputGroupButton,
InputGroupInput, InputGroupInput,
} from "@/components/ui/input-group"; } from "@/components/ui/input-group";
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
const Combobox = ComboboxPrimitive.Root; const Combobox = ComboboxPrimitive.Root;
+3 -4
View File
@@ -1,9 +1,8 @@
"use client"; "use client";
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk"; import { Command as CommandPrimitive } from "cmdk";
import { CheckIcon, SearchIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import type * as React from "react";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -12,7 +11,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"; import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon, CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
function Command({ function Command({
className, className,
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu"; import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";
import { CheckIcon, ChevronRightIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronRightIcon, CheckIcon } from "lucide-react";
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) { function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />; return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: DialogPrimitive.Root.Props) { function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />; return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer"; import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Menu as MenuPrimitive } from "@base-ui/react/menu"; import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { CheckIcon, ChevronRightIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronRightIcon, CheckIcon } from "lucide-react";
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />; return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
+9 -9
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import { useMemo } from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { useMemo } from "react";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return ( return (
@@ -52,7 +51,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
} }
const fieldVariants = cva( const fieldVariants = cva(
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive", "group/field flex w-full gap-2 border-0 p-0 m-0 data-[invalid=true]:text-destructive",
{ {
variants: { variants: {
orientation: { orientation: {
@@ -73,10 +72,9 @@ function Field({
className, className,
orientation = "vertical", orientation = "vertical",
...props ...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) { }: React.ComponentProps<"fieldset"> & VariantProps<typeof fieldVariants>) {
return ( return (
<div <fieldset
role="group"
data-slot="field" data-slot="field"
data-orientation={orientation} data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)} className={cn(fieldVariants({ orientation }), className)}
@@ -194,7 +192,7 @@ function FieldError({
...new Map(errors.map((error) => [error?.message, error])).values(), ...new Map(errors.map((error) => [error?.message, error])).values(),
]; ];
if (uniqueErrors?.length == 1) { if (uniqueErrors?.length === 1) {
return uniqueErrors[0]?.message; return uniqueErrors[0]?.message;
} }
@@ -202,7 +200,9 @@ function FieldError({
<ul className="ml-4 flex list-disc flex-col gap-1"> <ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map( {uniqueErrors.map(
(error, index) => (error, index) =>
error?.message && <li key={index}>{error.message}</li>, error?.message && (
<li key={`${error.message}-${index}`}>{error.message}</li>
),
)} )}
</ul> </ul>
); );
File diff suppressed because one or more lines are too long
+3 -4
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp"; import { OTPInput, OTPInputContext } from "input-otp";
import { cn } from "@/lib/utils";
import { MinusIcon } from "lucide-react"; import { MinusIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function InputOTP({ function InputOTP({
className, className,
@@ -75,7 +74,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="input-otp-separator" data-slot="input-otp-separator"
className="flex items-center [&_svg:not([class*='size-'])]:size-4" className="flex items-center [&_svg:not([class*='size-'])]:size-4"
role="separator" aria-hidden="true"
{...props} {...props}
> >
<MinusIcon /> <MinusIcon />
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { Input as InputPrimitive } from "@base-ui/react/input"; import { Input as InputPrimitive } from "@base-ui/react/input";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+4 -6
View File
@@ -1,15 +1,13 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { function ItemGroup({ className, ...props }: React.ComponentProps<"ul">) {
return ( return (
<div <ul
role="list"
data-slot="item-group" data-slot="item-group"
className={cn( className={cn(
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2", "group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
+2 -1
View File
@@ -1,11 +1,12 @@
"use client"; "use client";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<"label">) { function Label({ className, ...props }: React.ComponentProps<"label">) {
return ( return (
/* biome-ignore lint/a11y/noLabelWithoutControl: reusable label component, htmlFor or wrapping input provided by consumer */
<label <label
data-slot="label" data-slot="label"
className={cn( className={cn(
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+3 -4
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Menu as MenuPrimitive } from "@base-ui/react/menu"; import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar"; import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import type * as React from "react";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -20,7 +19,7 @@ import {
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { CheckIcon } from "lucide-react"; import { cn } from "@/lib/utils";
function Menubar({ className, ...props }: MenubarPrimitive.Props) { function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return ( return (
+3 -4
View File
@@ -1,16 +1,15 @@
"use client"; "use client";
import * as React from "react";
import { import {
MessageScroller as MessageScrollerPrimitive, MessageScroller as MessageScrollerPrimitive,
useMessageScroller, useMessageScroller,
useMessageScrollerScrollable, useMessageScrollerScrollable,
useMessageScrollerVisibility, useMessageScrollerVisibility,
} from "@shadcn/react/message-scroller"; } from "@shadcn/react/message-scroller";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ArrowDownIcon } from "lucide-react"; import { ArrowDownIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function MessageScrollerProvider( function MessageScrollerProvider(
props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>, props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>,
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,7 +1,6 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & { type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default"; size?: "sm" | "default";
+1 -2
View File
@@ -1,8 +1,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"; import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu";
import { cva } from "class-variance-authority"; import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function NavigationMenu({ function NavigationMenu({
align = "start", align = "start",
+3 -5
View File
@@ -1,17 +1,15 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
} from "lucide-react"; } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Pagination({ className, ...props }: React.ComponentProps<"nav">) { function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return ( return (
<nav <nav
role="navigation"
aria-label="pagination" aria-label="pagination"
data-slot="pagination" data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)} className={cn("mx-auto flex w-full justify-center", className)}
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import * as React from "react";
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
-1
View File
@@ -1,6 +1,5 @@
"use client"; "use client";
import * as React from "react";
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area"; import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client"; "use client";
import * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select"; import { Select as SelectPrimitive } from "@base-ui/react/select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"; import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Sheet({ ...props }: SheetPrimitive.Root.Props) { function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />; return <SheetPrimitive.Root data-slot="sheet" {...props} />;
+16 -8
View File
@@ -1,12 +1,10 @@
"use client"; "use client";
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props"; import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render"; import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";
import { useIsMobile } from "@/hooks/use-mobile"; import * as React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
@@ -23,10 +21,20 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { PanelLeftIcon } from "lucide-react"; import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
function setCookie(name: string, value: boolean, maxAge: number) {
Reflect.set(
document,
"cookie",
`${name}=${value}; path=/; max-age=${maxAge}`,
);
}
const SIDEBAR_WIDTH = "16rem"; const SIDEBAR_WIDTH = "16rem";
const SIDEBAR_WIDTH_MOBILE = "18rem"; const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_WIDTH_ICON = "3rem";
@@ -83,7 +91,7 @@ function SidebarProvider({
} }
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; setCookie(SIDEBAR_COOKIE_NAME, openState, SIDEBAR_COOKIE_MAX_AGE);
}, },
[setOpenProp, open], [setOpenProp, open],
); );
@@ -91,7 +99,7 @@ function SidebarProvider({
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]); }, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
@@ -123,7 +131,7 @@ function SidebarProvider({
setOpenMobile, setOpenMobile,
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar], [state, open, setOpen, isMobile, openMobile, toggleSidebar],
); );
return ( return (
+2 -2
View File
@@ -37,10 +37,10 @@ function Slider({
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full" className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/> />
</SliderPrimitive.Track> </SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => ( {_values.map((_, i) => (
<SliderPrimitive.Thumb <SliderPrimitive.Thumb
data-slot="slider-thumb" data-slot="slider-thumb"
key={index} key={i}
className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50" className="relative block size-3 shrink-0 rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50"
/> />
))} ))}
+4 -4
View File
@@ -1,14 +1,14 @@
"use client"; "use client";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import { import {
CircleCheckIcon, CircleCheckIcon,
InfoIcon, InfoIcon,
TriangleAlertIcon,
OctagonXIcon,
Loader2Icon, Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"; } from "lucide-react";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme(); const { theme = "system" } = useTheme();
+1 -2
View File
@@ -1,11 +1,10 @@
import { cn } from "@/lib/utils";
import { Loader2Icon } from "lucide-react"; import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) { function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return ( return (
<Loader2Icon <Loader2Icon
data-slot="spinner" data-slot="spinner"
role="status"
aria-label="Loading" aria-label="Loading"
className={cn("size-4 animate-spin", className)} className={cn("size-4 animate-spin", className)}
{...props} {...props}
+1 -1
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";

Some files were not shown because too many files have changed in this diff Show More