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
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
bun dev # Start dev server
bun run lint # Biome lint
bun run ci # Biome CI mode
```
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.
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.
Deployed as a Docker container behind Traefik on VPS. See `infra/` in the monorepo root for compose files and Traefik config.
+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": {
"enabled": true,
"clientKind": "git",
@@ -17,7 +17,7 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"preset": "recommended",
"suspicious": {
"noUnknownAtRules": "off"
}
@@ -27,6 +27,21 @@
"react": "recommended"
}
},
"overrides": [
{
"includes": ["src/components/ui/**"],
"linter": {
"rules": {
"security": {
"noDangerouslySetInnerHtml": "off"
},
"suspicious": {
"noArrayIndexKey": "off"
}
}
}
}
],
"assist": {
"actions": {
"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",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^1.25.0",
"lucide-react": "^1.26.0",
"next": "16.2.11",
"next-themes": "^0.4.6",
"react": "19.2.4",
"react": "19.2.8",
"react-day-picker": "^10.0.1",
"react-dom": "19.2.4",
"react-dom": "19.2.8",
"react-resizable-panels": "^4.12.2",
"recharts": "3.8.0",
"recharts": "3.10.0",
"shadcn": "^4.14.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
"@biomejs/biome": "2.5.5",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/node": "^26",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4",
"typescript": "^5"
"typescript": "^5.9.3"
},
"ignoreScripts": [
"sharp",
+5 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import http from "node:http";
import { NextResponse } from "next/server";
const JAEGER = "http://jaeger:16686";
const PROMETHEUS = "http://prometheus:9090";
@@ -31,7 +31,9 @@ function dockerFetch(path: string): Promise<unknown> {
http
.get({ socketPath: DOCKER_SOCK, path }, (res) => {
let data = "";
res.on("data", (c: string) => (data += c));
res.on("data", (c: string) => {
data += c;
});
res.on("end", () => {
try {
resolve(JSON.parse(data));
@@ -129,7 +131,7 @@ async function promRange(query: string, steps = 20): Promise<number[]> {
if (!result?.[0]?.values) return [];
return result[0].values.map((v) => {
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 {
@apply ring-[3px];
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 {
@apply ring-[3px];
+6 -1
View File
@@ -32,7 +32,12 @@ export default function RootLayout({
suppressHydrationWarning
>
<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 />
{children}
</ThemeProvider>
+8 -8
View File
@@ -98,15 +98,15 @@ export default function Home() {
I&rsquo;m a Teknik Informatika student passionate about backend
engineering, distributed systems, and infrastructure. I spend my
time building RESTful APIs, learning container orchestration with
Docker &amp; Traefik, and exploring observability with OpenTelemetry
&amp; Jaeger.
Docker &amp; Traefik, and exploring observability with
OpenTelemetry &amp; Jaeger.
</p>
<p>
I was the Back-End lead on the ZeaVis Edu capstone team (&ldquo;AI
for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild),
a 5-person team with task division across Back-End, Front-End,
and Machine Learning. I handled the system architecture, API
design, RESTful API development, and Docker deployment on VPS.
for Smart Education&rdquo; by Pijak &times; IBM SkillsBuild), a
5-person team with task division across Back-End, Front-End, and
Machine Learning. I handled the system architecture, API design,
RESTful API development, and Docker deployment on VPS.
</p>
</div>
</div>
@@ -260,8 +260,8 @@ export default function Home() {
</p>
<p className="max-w-md text-sm text-muted-foreground">
The dashboard tracks Docker services, Traefik request metrics,
Prometheus system resources, and Jaeger traces — all running on
my VPS deployment.
Prometheus system resources, and Jaeger traces — all running on my
VPS deployment.
</p>
<a href="/dashboard" className={cn(buttonVariants())}>
Live Dashboard
+1 -2
View File
@@ -1,7 +1,6 @@
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
import { cn } from "@/lib/utils";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
return (
+2 -3
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { cn } from "@/lib/utils";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.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 type * as React from "react";
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 { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
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",
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react";
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 { useRender } from "@base-ui/react/use-render";
import { cn } from "@/lib/utils";
import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
return (
@@ -63,8 +62,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
+4 -6
View File
@@ -1,12 +1,11 @@
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
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: {
orientation: {
@@ -26,10 +25,9 @@ function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
}: React.ComponentProps<"fieldset"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
<fieldset
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
+10 -11
View File
@@ -1,20 +1,19 @@
"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 {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronDownIcon,
} 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({
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";
+9 -11
View File
@@ -1,13 +1,12 @@
"use client";
import * as React from "react";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
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 UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -118,16 +117,16 @@ function Carousel({
canScrollNext,
}}
>
<div
{/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: <section> has implicit role="region" which supports aria-roledescription */}
<section
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</section>
</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();
return (
<div
role="group"
<fieldset
aria-roledescription="slide"
data-slot="carousel-item"
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",
className,
)}
+11 -7
View File
@@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as RechartsPrimitive from "recharts";
import type { TooltipValueType } from "recharts";
import * as RechartsPrimitive from "recharts";
import { cn } from "@/lib/utils";
@@ -200,13 +200,17 @@ function ChartTooltipContent({
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const itemKey = `${nameKey ?? item.name ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(
config,
item,
itemKey,
);
const indicatorColor = color ?? item.payload?.fill ?? item.color;
return (
<div
key={index}
key={itemKey}
className={cn(
"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",
@@ -299,12 +303,12 @@ function ChartLegendContent({
{payload
.filter((item) => item.type !== "none")
.map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`;
const itemConfig = getPayloadConfigFromPayload(config, item, key);
const itemKey = `${nameKey ?? item.dataKey ?? "value"}-${index}`;
const itemConfig = getPayloadConfigFromPayload(config, item, itemKey);
return (
<div
key={index}
key={itemKey}
className={cn(
"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";
import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
return (
+3 -4
View File
@@ -1,9 +1,8 @@
"use client";
import * as React from "react";
import { Combobox as ComboboxPrimitive } from "@base-ui/react";
import { cn } from "@/lib/utils";
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
@@ -11,7 +10,7 @@ import {
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
const Combobox = ComboboxPrimitive.Root;
+3 -4
View File
@@ -1,9 +1,8 @@
"use client";
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk";
import { cn } from "@/lib/utils";
import { CheckIcon, SearchIcon } from "lucide-react";
import type * as React from "react";
import {
Dialog,
DialogContent,
@@ -12,7 +11,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
import { SearchIcon, CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Command({
className,
+2 -3
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
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 { ChevronRightIcon, CheckIcon } from "lucide-react";
function ContextMenu({ ...props }: ContextMenuPrimitive.Root.Props) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client";
import * as React from "react";
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 type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
import * as React from "react";
import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
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 { ChevronRightIcon, CheckIcon } from "lucide-react";
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
+9 -9
View File
@@ -1,11 +1,10 @@
"use client";
import { useMemo } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { useMemo } from "react";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
@@ -52,7 +51,7 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
}
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: {
orientation: {
@@ -73,10 +72,9 @@ function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
}: React.ComponentProps<"fieldset"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
<fieldset
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
@@ -194,7 +192,7 @@ function FieldError({
...new Map(errors.map((error) => [error?.message, error])).values(),
];
if (uniqueErrors?.length == 1) {
if (uniqueErrors?.length === 1) {
return uniqueErrors[0]?.message;
}
@@ -202,7 +200,9 @@ function FieldError({
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>,
error?.message && (
<li key={`${error.message}-${index}`}>{error.message}</li>
),
)}
</ul>
);
File diff suppressed because one or more lines are too long
+3 -4
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp";
import { cn } from "@/lib/utils";
import { MinusIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function InputOTP({
className,
@@ -75,7 +74,7 @@ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
<div
data-slot="input-otp-separator"
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
role="separator"
aria-hidden="true"
{...props}
>
<MinusIcon />
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { Input as InputPrimitive } from "@base-ui/react/input";
import type * as React from "react";
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 { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type * as React from "react";
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 (
<div
role="list"
<ul
data-slot="item-group"
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",
+2 -1
View File
@@ -1,11 +1,12 @@
"use client";
import * as React from "react";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<"label">) {
return (
/* biome-ignore lint/a11y/noLabelWithoutControl: reusable label component, htmlFor or wrapping input provided by consumer */
<label
data-slot="label"
className={cn(
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
+3 -4
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
import { cn } from "@/lib/utils";
import { CheckIcon } from "lucide-react";
import type * as React from "react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -20,7 +19,7 @@ import {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function Menubar({ className, ...props }: MenubarPrimitive.Props) {
return (
+3 -4
View File
@@ -1,16 +1,15 @@
"use client";
import * as React from "react";
import {
MessageScroller as MessageScrollerPrimitive,
useMessageScroller,
useMessageScrollerScrollable,
useMessageScrollerVisibility,
} from "@shadcn/react/message-scroller";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { ArrowDownIcon } from "lucide-react";
import type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function MessageScrollerProvider(
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";
+2 -3
View File
@@ -1,7 +1,6 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils";
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default";
+1 -2
View File
@@ -1,8 +1,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu";
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "@/lib/utils";
function NavigationMenu({
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 {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} 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">) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
import type * as React from "react";
import { cn } from "@/lib/utils";
-1
View File
@@ -1,6 +1,5 @@
"use client";
import * as React from "react";
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
import { cn } from "@/lib/utils";
+2 -3
View File
@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
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 { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
const Select = SelectPrimitive.Root;
+3 -4
View File
@@ -1,11 +1,10 @@
"use client";
import * as React from "react";
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 type * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
+16 -8
View File
@@ -1,12 +1,10 @@
"use client";
import * as React from "react";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import { PanelLeftIcon } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
@@ -23,10 +21,20 @@ import {
TooltipContent,
TooltipTrigger,
} 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_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_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
@@ -83,7 +91,7 @@ function SidebarProvider({
}
// 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],
);
@@ -91,7 +99,7 @@ function SidebarProvider({
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]);
}, [isMobile, setOpen]);
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
@@ -123,7 +131,7 @@ function SidebarProvider({
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
);
return (
+2 -2
View File
@@ -37,10 +37,10 @@ function Slider({
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
{_values.map((_, i) => (
<SliderPrimitive.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"
/>
))}
+4 -4
View File
@@ -1,14 +1,14 @@
"use client";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import {
CircleCheckIcon,
InfoIcon,
TriangleAlertIcon,
OctagonXIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react";
import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
+1 -2
View File
@@ -1,11 +1,10 @@
import { cn } from "@/lib/utils";
import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Loader2Icon
data-slot="spinner"
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
+1 -1
View File
@@ -1,6 +1,6 @@
"use client";
import * as React from "react";
import type * as React from "react";
import { cn } from "@/lib/utils";

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