Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28a86dcbff | ||
| 7cd826b052 | |||
|
|
da20e34dc9 | ||
|
|
a43b5a96bb | ||
|
|
48aaa262c1 | ||
| 660839d854 | |||
|
|
6d23d601a8 | ||
|
|
7405f2cb88 | ||
|
|
d415615ad7 | ||
|
|
6cd05ca4b4 | ||
| 97b2093b86 | |||
|
|
c0aa618dfa |
@@ -42,8 +42,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=local,src=/cache
|
cache-from: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache
|
||||||
cache-to: type=local,dest=/cache,mode=max
|
cache-to: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache,mode=max
|
||||||
# platforms: linux/amd64,linux/arm64
|
# platforms: linux/amd64,linux/arm64
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ WORKDIR /app
|
|||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# 复制源代码
|
# 复制源代码
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "litek",
|
"name": "litek",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.2",
|
"version": "0.0.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"release:patch": "npm version patch && git push --follow-tags",
|
||||||
|
"release:minor": "npm version minor && git push --follow-tags",
|
||||||
|
"release:major": "npm version major && git push --follow-tags"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
@@ -19,9 +22,11 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.548.0",
|
"lucide-react": "^0.548.0",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.9.4",
|
"react-router-dom": "^7.9.4",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
|
|||||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.1.6
|
specifier: ^5.1.6
|
||||||
version: 5.1.6
|
version: 5.1.6
|
||||||
|
next-themes:
|
||||||
|
specifier: ^0.4.6
|
||||||
|
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
react:
|
react:
|
||||||
specifier: ^19.1.1
|
specifier: ^19.1.1
|
||||||
version: 19.2.0
|
version: 19.2.0
|
||||||
@@ -47,6 +50,9 @@ importers:
|
|||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^7.9.4
|
specifier: ^7.9.4
|
||||||
version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
sonner:
|
||||||
|
specifier: ^2.0.7
|
||||||
|
version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
@@ -1280,6 +1286,12 @@ packages:
|
|||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
next-themes@0.4.6:
|
||||||
|
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||||
|
|
||||||
node-releases@2.0.26:
|
node-releases@2.0.26:
|
||||||
resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==}
|
resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==}
|
||||||
|
|
||||||
@@ -1472,6 +1484,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
sonner@2.0.7:
|
||||||
|
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2702,6 +2720,11 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
|
||||||
node-releases@2.0.26: {}
|
node-releases@2.0.26: {}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
@@ -2854,6 +2877,11 @@ snapshots:
|
|||||||
|
|
||||||
shebang-regex@3.0.0: {}
|
shebang-regex@3.0.0: {}
|
||||||
|
|
||||||
|
sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|||||||
70
src/components/tool/base64.tsx
Normal file
70
src/components/tool/base64.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { useState, type FC } from "react";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
||||||
|
|
||||||
|
const Tool: FC = () => {
|
||||||
|
const [decoded, setDecoded] = useState<string>("");
|
||||||
|
const [encoded, setEncoded] = useState<string>("");
|
||||||
|
|
||||||
|
const encode = () => {
|
||||||
|
try {
|
||||||
|
const encoded64 = btoa(decoded);
|
||||||
|
setEncoded(encoded64);
|
||||||
|
setDecoded("");
|
||||||
|
toast.success("encoded successfully");
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.error("encoding failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const decode = () => {
|
||||||
|
try {
|
||||||
|
const decoded64 = atob(encoded);
|
||||||
|
setDecoded(decoded64);
|
||||||
|
setEncoded("");
|
||||||
|
toast.success("decoded successfully");
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.error("decoding failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[50vh] flex flex-row gap-4 pt-[20vh]">
|
||||||
|
<Textarea
|
||||||
|
className="flex-1 resize-none"
|
||||||
|
placeholder="Enter the original text"
|
||||||
|
value={decoded}
|
||||||
|
onChange={(e) => setDecoded(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-2 justify-center">
|
||||||
|
<Button onClick={encode}>
|
||||||
|
<ArrowRightIcon className="size-4" />
|
||||||
|
Encode
|
||||||
|
</Button>
|
||||||
|
<Button onClick={decode}>
|
||||||
|
<ArrowLeftIcon className="size-4" />
|
||||||
|
Decode
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
className="flex-1 resize-none"
|
||||||
|
placeholder="Enter the Base64 encoded text"
|
||||||
|
value={encoded}
|
||||||
|
onChange={(e) => setEncoded(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tool;
|
||||||
|
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
import { Hash } from 'lucide-react'
|
import type { ReactNode } from 'react';
|
||||||
|
import { FileJson, Hash, Binary } from 'lucide-react'
|
||||||
|
|
||||||
import type { Tool } from "./type";
|
import UUID from './uuid'
|
||||||
import { UUID } from './uuid'
|
import JSON from './json'
|
||||||
|
import Base64 from './base64'
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
description: string;
|
||||||
|
component: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export const tools: Tool[] = [
|
export const tools: Tool[] = [
|
||||||
{
|
{
|
||||||
@@ -10,5 +20,19 @@ export const tools: Tool[] = [
|
|||||||
description: "Generate a UUID",
|
description: "Generate a UUID",
|
||||||
icon: <Hash />,
|
icon: <Hash />,
|
||||||
component: <UUID />,
|
component: <UUID />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "json",
|
||||||
|
name: "JSON Formatter",
|
||||||
|
description: "Format and validate JSON",
|
||||||
|
icon: <FileJson />,
|
||||||
|
component: <JSON />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "base64",
|
||||||
|
name: "Base64 Encoder/Decoder",
|
||||||
|
description: "Encode and decode Base64",
|
||||||
|
icon: <Binary />,
|
||||||
|
component: <Base64 />,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
48
src/components/tool/json.tsx
Normal file
48
src/components/tool/json.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState, type FC } from "react";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const Tool: FC = () => {
|
||||||
|
const [json, setJson] = useState<string>("");
|
||||||
|
|
||||||
|
const validateJson = () => {
|
||||||
|
try {
|
||||||
|
JSON.parse(json);
|
||||||
|
return true;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.error("Invalid JSON");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const minifyJson = () => {
|
||||||
|
if (!validateJson()) return;
|
||||||
|
const formattedJson = JSON.stringify(JSON.parse(json), null, 0);
|
||||||
|
setJson(formattedJson);
|
||||||
|
toast.success("Minified successfully");
|
||||||
|
};
|
||||||
|
|
||||||
|
const prettifyJson = () => {
|
||||||
|
if (!validateJson()) return;
|
||||||
|
const formattedJson = JSON.stringify(JSON.parse(json), null, 2);
|
||||||
|
setJson(formattedJson);
|
||||||
|
toast.success("JSON prettified successfully");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col gap-4">
|
||||||
|
<Textarea className="flex-1 w-full resize-none" placeholder="Enter your JSON here" value={json} onChange={(e) => setJson(e.target.value)} />
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Button onClick={minifyJson}>Minify</Button>
|
||||||
|
<Button onClick={prettifyJson}>Pretty</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tool;
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import type { ReactNode } from "react";
|
|
||||||
|
|
||||||
export interface Tool {
|
|
||||||
path: string;
|
|
||||||
name: string;
|
|
||||||
icon: ReactNode;
|
|
||||||
description: string;
|
|
||||||
component: ReactNode;
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import { type FC } from "react";
|
|||||||
import * as uuid from 'uuid'
|
import * as uuid from 'uuid'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
export const UUID: FC = () => {
|
const Tool: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<span className="text-sm text-muted-foreground">Refresh the page to generate new UUID</span>
|
<span className="text-sm text-muted-foreground">Refresh the page to generate new UUID</span>
|
||||||
@@ -19,4 +19,6 @@ export const UUID: FC = () => {
|
|||||||
<span>{nanoid()}</span>
|
<span>{nanoid()}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default Tool;
|
||||||
66
src/components/ui/alert.tsx
Normal file
66
src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-card text-card-foreground",
|
||||||
|
destructive:
|
||||||
|
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Alert({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert"
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-title"
|
||||||
|
className={cn(
|
||||||
|
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="alert-description"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
38
src/components/ui/sonner.tsx
Normal file
38
src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
CircleCheckIcon,
|
||||||
|
InfoIcon,
|
||||||
|
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()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
icons={{
|
||||||
|
success: <CircleCheckIcon className="size-4" />,
|
||||||
|
info: <InfoIcon className="size-4" />,
|
||||||
|
warning: <TriangleAlertIcon className="size-4" />,
|
||||||
|
error: <OctagonXIcon className="size-4" />,
|
||||||
|
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||||
|
}}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--normal-bg": "var(--popover)",
|
||||||
|
"--normal-text": "var(--popover-foreground)",
|
||||||
|
"--normal-border": "var(--border)",
|
||||||
|
"--border-radius": "var(--radius)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toaster }
|
||||||
18
src/components/ui/textarea.tsx
Normal file
18
src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
data-slot="textarea"
|
||||||
|
className={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
@@ -7,12 +7,12 @@ import { AppSidebar } from "@/components/sidebar";
|
|||||||
export const Layout: FC = () => (
|
export const Layout: FC = () => (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<div className="p-4 flex flex-col w-full h-[100vh]">
|
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
|
||||||
<nav className="flex items-center justify-between">
|
<nav className="flex items-center justify-between">
|
||||||
<SidebarTrigger className="size-10" />
|
<SidebarTrigger className="size-10" />
|
||||||
<div role="actions" />
|
<div role="actions" />
|
||||||
</nav>
|
</nav>
|
||||||
<main className="flex-1 overflow-auto p-4">
|
<main className="flex-1 overflow-auto p-4 overflow-hidden">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { AppRouter } from './router'
|
import { AppRouter } from './router'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
|
<Toaster />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user