first_commit

This commit is contained in:
Tom-lyz
2025-08-26 17:41:23 +08:00
commit 467962dbb2
39 changed files with 1664 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
pnpm-lock.yaml
yarn.lock
package-lock.json
bun.lockb

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=true

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"tailwindCSS.experimental.classRegex": [
["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Next UI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

50
README.md Normal file
View File

@@ -0,0 +1,50 @@
# Vite & HeroUI Template
This is a template for creating applications using Vite and HeroUI (v2).
[Try it on CodeSandbox](https://githubbox.com/frontio-ai/vite-template)
## Technologies Used
- [Vite](https://vitejs.dev/guide/)
- [HeroUI](https://heroui.com)
- [Tailwind CSS](https://tailwindcss.com)
- [Tailwind Variants](https://tailwind-variants.org)
- [TypeScript](https://www.typescriptlang.org)
- [Framer Motion](https://www.framer.com/motion)
## How to Use
To clone the project, run the following command:
```bash
git clone https://github.com/frontio-ai/vite-template.git
```
### Install dependencies
You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`:
```bash
npm install
```
### Run the development server
```bash
npm run dev
```
### Setup pnpm (optional)
If you are using `pnpm`, you need to add the following code to your `.npmrc` file:
```bash
public-hoist-pattern[]=*@heroui/*
```
After modifying the `.npmrc` file, you need to run `pnpm install` again to ensure that the dependencies are installed correctly.
## License
Licensed under the [MIT license](https://github.com/frontio-ai/vite-template/blob/main/LICENSE).

175
eslint.config.mjs Normal file
View File

@@ -0,0 +1,175 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig, globalIgnores } from "eslint/config";
import { fixupConfigRules, fixupPluginRules } from "@eslint/compat";
import react from "eslint-plugin-react";
import unusedImports from "eslint-plugin-unused-imports";
import _import from "eslint-plugin-import";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import jsxA11Y from "eslint-plugin-jsx-a11y";
import prettier from "eslint-plugin-prettier";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([
globalIgnores([
".now/*",
"**/*.css",
"**/.changeset",
"**/dist",
"esm/*",
"public/*",
"tests/*",
"scripts/*",
"**/*.config.js",
"**/.DS_Store",
"**/node_modules",
"**/coverage",
"**/.next",
"**/build",
"!**/.commitlintrc.cjs",
"!**/.lintstagedrc.cjs",
"!**/jest.config.js",
"!**/plopfile.js",
"!**/react-shim.js",
"!**/tsup.config.ts",
]),
{
extends: fixupConfigRules(
compat.extends(
"plugin:react/recommended",
"plugin:prettier/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
),
),
plugins: {
react: fixupPluginRules(react),
"unused-imports": unusedImports,
import: fixupPluginRules(_import),
"@typescript-eslint": typescriptEslint,
"jsx-a11y": fixupPluginRules(jsxA11Y),
prettier: fixupPluginRules(prettier),
},
languageOptions: {
globals: {
...Object.fromEntries(
Object.entries(globals.browser).map(([key]) => [key, "off"]),
),
...globals.node,
},
parser: tsParser,
ecmaVersion: 12,
sourceType: "module",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
settings: {
react: {
version: "detect",
},
},
files: ["**/*.ts", "**/*.tsx"],
rules: {
"no-console": "warn",
"react/prop-types": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"jsx-a11y/click-events-have-key-events": "warn",
"jsx-a11y/interactive-supports-focus": "warn",
"prettier/prettier": "warn",
"no-unused-vars": "off",
"unused-imports/no-unused-vars": "off",
"unused-imports/no-unused-imports": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
args: "after-used",
ignoreRestSiblings: false,
argsIgnorePattern: "^_.*?$",
},
],
"import/order": [
"warn",
{
groups: [
"type",
"builtin",
"object",
"external",
"internal",
"parent",
"sibling",
"index",
],
pathGroups: [
{
pattern: "~/**",
group: "external",
position: "after",
},
],
"newlines-between": "always",
},
],
"react/self-closing-comp": "warn",
"react/jsx-sort-props": [
"warn",
{
callbacksLast: true,
shorthandFirst: true,
noSortAlphabetically: false,
reservedFirst: true,
},
],
"padding-line-between-statements": [
"warn",
{
blankLine: "always",
prev: "*",
next: "return",
},
{
blankLine: "always",
prev: ["const", "let", "var"],
next: "*",
},
{
blankLine: "any",
prev: ["const", "let", "var"],
next: ["const", "let", "var"],
},
],
},
},
]);

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

28
index.html Normal file
View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + HeroUI</title>
<meta key="title" content="Vite + HeroUI" property="og:title" />
<meta
content="Make beautiful websites regardless of your design experience."
property="og:description"
/>
<meta
content="Make beautiful websites regardless of your design experience."
name="description"
/>
<meta
key="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
name="viewport"
/>
<link href="/favicon.ico" rel="icon" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

65
package.json Normal file
View File

@@ -0,0 +1,65 @@
{
"name": "vite-template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint --fix",
"preview": "vite preview"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@heroui/button": "^2.2.24",
"@heroui/code": "^2.2.18",
"@heroui/dropdown": "^2.3.24",
"@heroui/input": "^2.4.25",
"@heroui/kbd": "^2.2.19",
"@heroui/link": "^2.2.21",
"@heroui/navbar": "^2.2.22",
"@heroui/react": "^2.8.2",
"@heroui/snippet": "^2.2.25",
"@heroui/switch": "^2.2.22",
"@heroui/system": "^2.4.20",
"@heroui/theme": "^2.4.20",
"@heroui/use-theme": "2.1.10",
"@react-aria/visually-hidden": "3.8.26",
"@react-types/shared": "3.31.0",
"@tailwindcss/postcss": "4.1.11",
"@tailwindcss/vite": "4.1.11",
"clsx": "2.1.1",
"framer-motion": "11.18.2",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.23.0",
"tailwind-variants": "2.0.1",
"tailwindcss": "4.1.11"
},
"devDependencies": {
"@eslint/compat": "1.2.8",
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.25.1",
"@types/node": "20.5.7",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"@vitejs/plugin-react": "^4.7.0",
"eslint": "9.25.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-unused-imports": "4.1.4",
"globals": "16.0.0",
"postcss": "8.5.6",
"prettier": "3.5.3",
"typescript": "5.6.3",
"vite": "6.0.11",
"vite-tsconfig-paths": "5.1.4"
}
}

5
postcss.config.js Normal file
View File

@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

15
src/App.tsx Normal file
View File

@@ -0,0 +1,15 @@
import Navbar_create from "./Navbar";
import ChatbotInterface from "./new chatbot-interaction";
function App() {
return (
<>
<Navbar_create />
<ChatbotInterface/>
</>
);
}
export default App;

86
src/ChatbotInput.tsx Normal file
View File

@@ -0,0 +1,86 @@
import { Input } from "@heroui/react";
import { useState } from "react";
import {Button} from "@heroui/react";
interface ChatbotInputProps {
onSubmit?: (question: string) => void;
placeholder?: string;
maxHeight?: number;
minHeight?: number;
}
const ChatbotInput = ({
onSubmit,
placeholder = "请输入你的问题...",
minHeight = 18,
maxHeight = 120,
}: ChatbotInputProps) => {
const [inputValue, setInputValue] = useState("");
const [inputHeight, setInputHeight] = useState(minHeight);
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setInputValue(value);
const textarea = e.target;
textarea.style.height = `${minHeight}px`;
const newHeight = Math.min(textarea.scrollHeight, maxHeight);
setInputHeight(newHeight);
textarea.style.height = `${newHeight}px`;
};
const handleSubmit = () => {
const question = inputValue.trim();
if (question && onSubmit) {
onSubmit(question);
setInputValue("");
setInputHeight(18);
}
};
return (
<div
className="flex flex-col w-full max-w-2xl px-4 py-2
border border-gray-200 rounded-lg
hover:border-gray-300 focus-within:border-blue-400
transition-all transition-all duration-200 bg-white shadow-sm"
>
<textarea
value={inputValue}
onChange={handleInputChange}
placeholder={placeholder}
style={{
minHeight: `${minHeight}px`,
maxHeight: `${maxHeight}px`,
height: `${inputHeight}px`,
overflowY: inputHeight >= maxHeight ? "auto" : "hidden",
}}
className="flex flex-1 w-full border-0 shadow-none focus:ring-0
px-3 py-2 bg-transparent placeholder:text-gray-400
resize-none outline-none"
spellCheck={false}
/>
<div className="flex justify-end mt-1">
<Button
onPress={handleSubmit}
disabled={!inputValue.trim()}
className={`flex items-center justify-center w-8 h-8 rounded-full
transition-colors duration-200 cursor-pointer
${
inputValue.trim()
? "bg-blue-500 text-white hover:bg-blue-600"
: "bg-gray-100 text-gray-400 cursor-not-allowed"
}`}
aria-label="发送问题"
>
<span className="text-lg">{"-" + ">"}</span>
</Button>
</div>
</div>
);
};
export default ChatbotInput;

52
src/Left-drawer.tsx Normal file
View File

@@ -0,0 +1,52 @@
import React from "react";
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerBody,
DrawerFooter,
Button,
useDisclosure,
} from "@heroui/react";
export default function Left() {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
const [placement, setPlacement] = React.useState("left");
const handleOpen = (placement: React.SetStateAction<string>) => {
setPlacement(placement);
onOpen();
};
return (
<>
<div className="flex flex-wrap gap-3">
{["left"].map((placement) => (
<Button key={placement} className="capitalize" onPress={() => handleOpen(placement)}>
</Button>
))}
</div>
<Drawer isOpen={isOpen} placement={placement} onOpenChange={onOpenChange}>
<DrawerContent>
{(onClose) => (
<>
<DrawerHeader className="flex flex-col gap-1"></DrawerHeader>
<DrawerBody>
</DrawerBody>
<DrawerFooter>
<Button color="danger" variant="light" onPress={onClose}>
</Button>
<Button color="primary" onPress={onClose}>
Action
</Button>
</DrawerFooter>
</>
)}
</DrawerContent>
</Drawer>
</>
);
}

54
src/Navbar.tsx Normal file
View File

@@ -0,0 +1,54 @@
import {Navbar, NavbarBrand, NavbarContent, NavbarItem,Button,Link} from "@heroui/react";
import Left from "./Left-drawer";
export const AcmeLogo = () => {
return (
<svg fill="none" height="36" viewBox="0 0 32 32" width="36">
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
};
export default function Navbar_create() {
return (
<Navbar>
<NavbarBrand>
<AcmeLogo />
<p className="font-bold text-inherit">ChatBot</p>
</NavbarBrand>
<NavbarContent className="hidden sm:flex gap-4" justify="center">
<NavbarItem>
<Link color="foreground" href="#">
<Left 历史对话/>
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link aria-current="page" href="#">
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" href="#">
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" href="#" variant="flat">
</Button>
</NavbarItem>
</NavbarContent>
</Navbar>
);
}

0
src/Scroll Shadow.tsx Normal file
View File

186
src/components/icons.tsx Normal file
View File

@@ -0,0 +1,186 @@
import * as React from "react";
import { IconSvgProps } from "@/types";
export const Logo: React.FC<IconSvgProps> = ({
size = 36,
height,
...props
}) => (
<svg
fill="none"
height={size || height}
viewBox="0 0 32 32"
width={size || height}
{...props}
>
<path
clipRule="evenodd"
d="M17.6482 10.1305L15.8785 7.02583L7.02979 22.5499H10.5278L17.6482 10.1305ZM19.8798 14.0457L18.11 17.1983L19.394 19.4511H16.8453L15.1056 22.5499H24.7272L19.8798 14.0457Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export const DiscordIcon: React.FC<IconSvgProps> = ({
size = 24,
width,
height,
...props
}) => {
return (
<svg
height={size || height}
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
d="M14.82 4.26a10.14 10.14 0 0 0-.53 1.1 14.66 14.66 0 0 0-4.58 0 10.14 10.14 0 0 0-.53-1.1 16 16 0 0 0-4.13 1.3 17.33 17.33 0 0 0-3 11.59 16.6 16.6 0 0 0 5.07 2.59A12.89 12.89 0 0 0 8.23 18a9.65 9.65 0 0 1-1.71-.83 3.39 3.39 0 0 0 .42-.33 11.66 11.66 0 0 0 10.12 0q.21.18.42.33a10.84 10.84 0 0 1-1.71.84 12.41 12.41 0 0 0 1.08 1.78 16.44 16.44 0 0 0 5.06-2.59 17.22 17.22 0 0 0-3-11.59 16.09 16.09 0 0 0-4.09-1.35zM8.68 14.81a1.94 1.94 0 0 1-1.8-2 1.93 1.93 0 0 1 1.8-2 1.93 1.93 0 0 1 1.8 2 1.93 1.93 0 0 1-1.8 2zm6.64 0a1.94 1.94 0 0 1-1.8-2 1.93 1.93 0 0 1 1.8-2 1.92 1.92 0 0 1 1.8 2 1.92 1.92 0 0 1-1.8 2z"
fill="currentColor"
/>
</svg>
);
};
export const TwitterIcon: React.FC<IconSvgProps> = ({
size = 24,
width,
height,
...props
}) => {
return (
<svg
height={size || height}
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
d="M19.633 7.997c.013.175.013.349.013.523 0 5.325-4.053 11.461-11.46 11.461-2.282 0-4.402-.661-6.186-1.809.324.037.636.05.973.05a8.07 8.07 0 0 0 5.001-1.721 4.036 4.036 0 0 1-3.767-2.793c.249.037.499.062.761.062.361 0 .724-.05 1.061-.137a4.027 4.027 0 0 1-3.23-3.953v-.05c.537.299 1.16.486 1.82.511a4.022 4.022 0 0 1-1.796-3.354c0-.748.199-1.434.548-2.032a11.457 11.457 0 0 0 8.306 4.215c-.062-.3-.1-.611-.1-.923a4.026 4.026 0 0 1 4.028-4.028c1.16 0 2.207.486 2.943 1.272a7.957 7.957 0 0 0 2.556-.973 4.02 4.02 0 0 1-1.771 2.22 8.073 8.073 0 0 0 2.319-.624 8.645 8.645 0 0 1-2.019 2.083z"
fill="currentColor"
/>
</svg>
);
};
export const GithubIcon: React.FC<IconSvgProps> = ({
size = 24,
width,
height,
...props
}) => {
return (
<svg
height={size || height}
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
clipRule="evenodd"
d="M12.026 2c-5.509 0-9.974 4.465-9.974 9.974 0 4.406 2.857 8.145 6.821 9.465.499.09.679-.217.679-.481 0-.237-.008-.865-.011-1.696-2.775.602-3.361-1.338-3.361-1.338-.452-1.152-1.107-1.459-1.107-1.459-.905-.619.069-.605.069-.605 1.002.07 1.527 1.028 1.527 1.028.89 1.524 2.336 1.084 2.902.829.091-.645.351-1.085.635-1.334-2.214-.251-4.542-1.107-4.542-4.93 0-1.087.389-1.979 1.024-2.675-.101-.253-.446-1.268.099-2.64 0 0 .837-.269 2.742 1.021a9.582 9.582 0 0 1 2.496-.336 9.554 9.554 0 0 1 2.496.336c1.906-1.291 2.742-1.021 2.742-1.021.545 1.372.203 2.387.099 2.64.64.696 1.024 1.587 1.024 2.675 0 3.833-2.33 4.675-4.552 4.922.355.308.675.916.675 1.846 0 1.334-.012 2.41-.012 2.737 0 .267.178.577.687.479C19.146 20.115 22 16.379 22 11.974 22 6.465 17.535 2 12.026 2z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
};
export const MoonFilledIcon = ({
size = 24,
width,
height,
...props
}: IconSvgProps) => (
<svg
aria-hidden="true"
focusable="false"
height={size || height}
role="presentation"
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
d="M21.53 15.93c-.16-.27-.61-.69-1.73-.49a8.46 8.46 0 01-1.88.13 8.409 8.409 0 01-5.91-2.82 8.068 8.068 0 01-1.44-8.66c.44-1.01.13-1.54-.09-1.76s-.77-.55-1.83-.11a10.318 10.318 0 00-6.32 10.21 10.475 10.475 0 007.04 8.99 10 10 0 002.89.55c.16.01.32.02.48.02a10.5 10.5 0 008.47-4.27c.67-.93.49-1.519.32-1.79z"
fill="currentColor"
/>
</svg>
);
export const SunFilledIcon = ({
size = 24,
width,
height,
...props
}: IconSvgProps) => (
<svg
aria-hidden="true"
focusable="false"
height={size || height}
role="presentation"
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<g fill="currentColor">
<path d="M19 12a7 7 0 11-7-7 7 7 0 017 7z" />
<path d="M12 22.96a.969.969 0 01-1-.96v-.08a1 1 0 012 0 1.038 1.038 0 01-1 1.04zm7.14-2.82a1.024 1.024 0 01-.71-.29l-.13-.13a1 1 0 011.41-1.41l.13.13a1 1 0 010 1.41.984.984 0 01-.7.29zm-14.28 0a1.024 1.024 0 01-.71-.29 1 1 0 010-1.41l.13-.13a1 1 0 011.41 1.41l-.13.13a1 1 0 01-.7.29zM22 13h-.08a1 1 0 010-2 1.038 1.038 0 011.04 1 .969.969 0 01-.96 1zM2.08 13H2a1 1 0 010-2 1.038 1.038 0 011.04 1 .969.969 0 01-.96 1zm16.93-7.01a1.024 1.024 0 01-.71-.29 1 1 0 010-1.41l.13-.13a1 1 0 011.41 1.41l-.13.13a.984.984 0 01-.7.29zm-14.02 0a1.024 1.024 0 01-.71-.29l-.13-.14a1 1 0 011.41-1.41l.13.13a1 1 0 010 1.41.97.97 0 01-.7.3zM12 3.04a.969.969 0 01-1-.96V2a1 1 0 012 0 1.038 1.038 0 01-1 1.04z" />
</g>
</svg>
);
export const HeartFilledIcon = ({
size = 24,
width,
height,
...props
}: IconSvgProps) => (
<svg
aria-hidden="true"
focusable="false"
height={size || height}
role="presentation"
viewBox="0 0 24 24"
width={size || width}
{...props}
>
<path
d="M12.62 20.81c-.34.12-.9.12-1.24 0C8.48 19.82 2 15.69 2 8.69 2 5.6 4.49 3.1 7.56 3.1c1.82 0 3.43.88 4.44 2.24a5.53 5.53 0 0 1 4.44-2.24C19.51 3.1 22 5.6 22 8.69c0 7-6.48 11.13-9.38 12.12Z"
fill="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
/>
</svg>
);
export const SearchIcon = (props: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...props}
>
<path
d="M11.5 21C16.7467 21 21 16.7467 21 11.5C21 6.25329 16.7467 2 11.5 2C6.25329 2 2 6.25329 2 11.5C2 16.7467 6.25329 21 11.5 21Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
<path
d="M22 22L20 20"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);

144
src/components/navbar.tsx Normal file
View File

@@ -0,0 +1,144 @@
import { Button } from "@heroui/button";
import { Kbd } from "@heroui/kbd";
import { Link } from "@heroui/link";
import { Input } from "@heroui/input";
import {
Navbar as HeroUINavbar,
NavbarBrand,
NavbarContent,
NavbarItem,
NavbarMenuToggle,
NavbarMenu,
NavbarMenuItem,
} from "@heroui/navbar";
import { link as linkStyles } from "@heroui/theme";
import clsx from "clsx";
import { siteConfig } from "@/config/site";
import { ThemeSwitch } from "@/components/theme-switch";
import {
TwitterIcon,
GithubIcon,
DiscordIcon,
HeartFilledIcon,
SearchIcon,
} from "@/components/icons";
import { Logo } from "@/components/icons";
export const Navbar = () => {
const searchInput = (
<Input
aria-label="Search"
classNames={{
inputWrapper: "bg-default-100",
input: "text-sm",
}}
endContent={
<Kbd className="hidden lg:inline-block" keys={["command"]}>
K
</Kbd>
}
labelPlacement="outside"
placeholder="Search..."
startContent={
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
}
type="search"
/>
);
return (
<HeroUINavbar maxWidth="xl" position="sticky">
<NavbarContent className="basis-1/5 sm:basis-full" justify="start">
<NavbarBrand className="gap-3 max-w-fit">
<Link
className="flex justify-start items-center gap-1"
color="foreground"
href="/"
>
<Logo />
<p className="font-bold text-inherit">ACME</p>
</Link>
</NavbarBrand>
<div className="hidden lg:flex gap-4 justify-start ml-2">
{siteConfig.navItems.map((item) => (
<NavbarItem key={item.href}>
<Link
className={clsx(
linkStyles({ color: "foreground" }),
"data-[active=true]:text-primary data-[active=true]:font-medium",
)}
color="foreground"
href={item.href}
>
{item.label}
</Link>
</NavbarItem>
))}
</div>
</NavbarContent>
<NavbarContent
className="hidden sm:flex basis-1/5 sm:basis-full"
justify="end"
>
<NavbarItem className="hidden sm:flex gap-2">
<Link isExternal href={siteConfig.links.twitter} title="Twitter">
<TwitterIcon className="text-default-500" />
</Link>
<Link isExternal href={siteConfig.links.discord} title="Discord">
<DiscordIcon className="text-default-500" />
</Link>
<Link isExternal href={siteConfig.links.github} title="GitHub">
<GithubIcon className="text-default-500" />
</Link>
<ThemeSwitch />
</NavbarItem>
<NavbarItem className="hidden lg:flex">{searchInput}</NavbarItem>
<NavbarItem className="hidden md:flex">
<Button
isExternal
as={Link}
className="text-sm font-normal text-default-600 bg-default-100"
href={siteConfig.links.sponsor}
startContent={<HeartFilledIcon className="text-danger" />}
variant="flat"
>
Sponsor
</Button>
</NavbarItem>
</NavbarContent>
<NavbarContent className="sm:hidden basis-1 pl-4" justify="end">
<Link isExternal href={siteConfig.links.github}>
<GithubIcon className="text-default-500" />
</Link>
<ThemeSwitch />
<NavbarMenuToggle />
</NavbarContent>
<NavbarMenu>
{searchInput}
<div className="mx-4 mt-2 flex flex-col gap-2">
{siteConfig.navMenuItems.map((item, index) => (
<NavbarMenuItem key={`${item}-${index}`}>
<Link
color={
index === 2
? "primary"
: index === siteConfig.navMenuItems.length - 1
? "danger"
: "foreground"
}
href="#"
size="lg"
>
{item.label}
</Link>
</NavbarMenuItem>
))}
</div>
</NavbarMenu>
</HeroUINavbar>
);
};

View File

@@ -0,0 +1,53 @@
import { tv } from "tailwind-variants";
export const title = tv({
base: "tracking-tight inline font-semibold",
variants: {
color: {
violet: "from-[#FF1CF7] to-[#b249f8]",
yellow: "from-[#FF705B] to-[#FFB457]",
blue: "from-[#5EA2EF] to-[#0072F5]",
cyan: "from-[#00b7fa] to-[#01cfea]",
green: "from-[#6FEE8D] to-[#17c964]",
pink: "from-[#FF72E1] to-[#F54C7A]",
foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]",
},
size: {
sm: "text-3xl lg:text-4xl",
md: "text-[2.3rem] lg:text-5xl",
lg: "text-4xl lg:text-6xl",
},
fullWidth: {
true: "w-full block",
},
},
defaultVariants: {
size: "md",
},
compoundVariants: [
{
color: [
"violet",
"yellow",
"blue",
"cyan",
"green",
"pink",
"foreground",
],
class: "bg-clip-text text-transparent bg-gradient-to-b",
},
],
});
export const subtitle = tv({
base: "w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full",
variants: {
fullWidth: {
true: "!w-full",
},
},
defaultVariants: {
fullWidth: true,
},
});

View File

@@ -0,0 +1,82 @@
import { FC, useState, useEffect } from "react";
import { VisuallyHidden } from "@react-aria/visually-hidden";
import { SwitchProps, useSwitch } from "@heroui/switch";
import clsx from "clsx";
import { useTheme } from "@heroui/use-theme";
import { SunFilledIcon, MoonFilledIcon } from "@/components/icons";
export interface ThemeSwitchProps {
className?: string;
classNames?: SwitchProps["classNames"];
}
export const ThemeSwitch: FC<ThemeSwitchProps> = ({
className,
classNames,
}) => {
const [isMounted, setIsMounted] = useState(false);
const { theme, setTheme } = useTheme();
const {
Component,
slots,
isSelected,
getBaseProps,
getInputProps,
getWrapperProps,
} = useSwitch({
isSelected: theme === "light",
onChange: () => setTheme(theme === "light" ? "dark" : "light"),
});
useEffect(() => {
setIsMounted(true);
}, [isMounted]);
// Prevent Hydration Mismatch
if (!isMounted) return <div className="w-6 h-6" />;
return (
<Component
aria-label={isSelected ? "Switch to dark mode" : "Switch to light mode"}
{...getBaseProps({
className: clsx(
"px-px transition-opacity hover:opacity-80 cursor-pointer",
className,
classNames?.base,
),
})}
>
<VisuallyHidden>
<input {...getInputProps()} />
</VisuallyHidden>
<div
{...getWrapperProps()}
className={slots.wrapper({
class: clsx(
[
"w-auto h-auto",
"bg-transparent",
"rounded-lg",
"flex items-center justify-center",
"group-data-[selected=true]:bg-transparent",
"!text-default-500",
"pt-px",
"px-0",
"mx-0",
],
classNames?.wrapper,
),
})}
>
{isSelected ? (
<MoonFilledIcon size={22} />
) : (
<SunFilledIcon size={22} />
)}
</div>
</Component>
);
};

69
src/config/site.ts Normal file
View File

@@ -0,0 +1,69 @@
export type SiteConfig = typeof siteConfig;
export const siteConfig = {
name: "Vite + HeroUI",
description: "Make beautiful websites regardless of your design experience.",
navItems: [
{
label: "Home",
href: "/",
},
{
label: "Docs",
href: "/docs",
},
{
label: "Pricing",
href: "/pricing",
},
{
label: "Blog",
href: "/blog",
},
{
label: "About",
href: "/about",
},
],
navMenuItems: [
{
label: "Profile",
href: "/profile",
},
{
label: "Dashboard",
href: "/dashboard",
},
{
label: "Projects",
href: "/projects",
},
{
label: "Team",
href: "/team",
},
{
label: "Calendar",
href: "/calendar",
},
{
label: "Settings",
href: "/settings",
},
{
label: "Help & Feedback",
href: "/help-feedback",
},
{
label: "Logout",
href: "/logout",
},
],
links: {
github: "https://github.com/frontio-ai/heroui",
twitter: "https://twitter.com/hero_ui",
docs: "https://heroui.com",
discord: "https://discord.gg/9b6yyZKmH4",
sponsor: "https://patreon.com/jrgarciadev",
},
};

29
src/layouts/default.tsx Normal file
View File

@@ -0,0 +1,29 @@
import { Link } from "@heroui/link";
import { Navbar } from "@/components/navbar";
export default function DefaultLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="relative flex flex-col h-screen">
<Navbar />
<main className="container mx-auto max-w-7xl px-6 flex-grow pt-16">
{children}
</main>
<footer className="w-full flex items-center justify-center py-3">
<Link
isExternal
className="flex items-center gap-1 text-current"
href="https://heroui.com"
title="heroui.com homepage"
>
<span className="text-default-600">Powered by</span>
<p className="text-primary">HeroUI</p>
</Link>
</footer>
</div>
);
}

17
src/main.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import { Provider } from "./provider.tsx";
import "@/styles/globals.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<Provider>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>,
);

View File

@@ -0,0 +1,282 @@
import React, { useState, useRef, useEffect } from "react";
import ChatbotInput from "./ChatbotInput";
import {Button} from "@heroui/react";
// import { li, use } from "framer-motion/client";
// import { user } from "@heroui/theme";
// import { text } from "stream/consumers";
// import addToast from "@heroui/react";
interface Message {
id: string;
text: string;
sender: "user" | "bot";
status: "sending" | "sent" | "received" | "error";
}
const ChatbotInterface: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([
{
id: "iniial",
text: "你好!我是智能聊天机器人",
sender: "bot",
status: "received",
},
]);
//复制chatbot消息函数
const copyText = async (text: string, messageId: string) => {
try {
await navigator.clipboard.writeText(text);
if (messageId) {
setCopySuccessId(messageId);
}
alert("复制成功");
} catch (err) {
console.error("复制失败:", err);
alert("复制失败,请手动复制");
}
};
//chatbot消息重新发送函数
const reasendMessage = async (messageId: string) => {
const userMessage = messages.find((msg) => msg.id === messageId);
if (!userMessage) {
return;
}
//使用fillter删除掉bot旧的消息
setMessages((prev) =>
prev.filter((msg) => !(msg.sender === "bot" && msg.id === messageId))
);
try {
const newReply = await new Promise((resolve) => {
setTimeout(() => {
resolve({
id: `${Date.now()}`,
text:
"你好,我是智能聊天机器人,很高兴为您服务。您刚才说的是:" +
userMessage.text,
sender: "bot",
status: "received",
});
}, 2000);
});
setMessages((prev) => [...prev, newReply as Message]);
} catch (err) {
console.error("重新发送失败:", err);
setMessages((prev) => [
...prev,
{
id: `${Date.now()}`,
text: "重新发送失败,请稍后再试",
sender: "bot",
status: "error",
},
]);
alert("重新发送失败,请稍后再试");
}
};
//新建对话功能
const handleNewChat = () => {
setMessages([
{
id: "iniial",
text: "你好!我是智能聊天机器人",
sender: "bot",
status: "received",
},
]);
};
const [copySuccessId, setCopySuccessId] = useState<string | null>(null);
//文本行字数限制以及自动换行
const addLineBreaksByCharLimit = (
text: string,
limit: number = 30,
): string => {
if (text || limit < 0) return text;
let result = "";
let currentLinetext = "";
for (const char of text){
if (char === "\n") {
result += currentLinetext + char;
currentLinetext = "";
continue;
}
if (currentLinetext.length >= limit) {
result += currentLinetext + "\n";
currentLinetext = char;
}else{
currentLinetext += char;
}
}
return result + currentLinetext;
};
const [isBotTyping, setIsBotTyping] = useState<boolean>(false);
//定位到消息列表最底部
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollFlow = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
//副作用钩子处理自动滚动
useEffect(() => {
scrollFlow();
}, [messages, isBotTyping]);
//处理用户消息
const handleUserMessage = (text: string) => {
//限制自动换行
const formattedQuestion = addLineBreaksByCharLimit(text, 30);
const userMessage: Message = {
id: `${Date.now()}`,
text: formattedQuestion,
sender: "user",
status: "sending",
};
setMessages((prevMessages) => [...prevMessages, userMessage]);
setTimeout(() => {
setMessages((prev) =>
prev.map((msg) =>
// 找到当前用户消息更新status其他消息不变
msg.id === userMessage.id ? { ...msg, status: "sent" } : msg
)
);
handBotMessage(text, "");
}, 1000);
};
//机器人回复
const handBotMessage = (userText: string, botMessageId: string) => {
setIsBotTyping(true);
setTimeout(() => {
const formattedQuestion = addLineBreaksByCharLimit(userText, 30);
const botMessageContent =
"你好,我是智能聊天机器人,很高兴为您服务。您刚才说的是:" +
formattedQuestion;
const newBotMessage: Message = {
id: `${Date.now()}` || botMessageId,
text: botMessageContent,
sender: "bot",
status: "received",
};
if (botMessageId) {
setMessages((prev) =>
prev.map((msg) => (msg.id === botMessageId ? newBotMessage : msg))
);
} else {
setMessages((prevMessages) => [...prevMessages, newBotMessage]);
}
setIsBotTyping(false);
}, 1000);
};
return (
<div className="max-w-2xl mx-auto w-full flex flex-col h-screen bg-gray-50 shadow-xl">
<main className="flex-1 overflow-y-auto p-4 space-y-6">
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.sender === "user" ? "justify-end" : "justify-start"} items-start`}
>
{message.sender === "bot" && (
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white flex-shrink-0 mr-2">
<span className="text-xs font-bold">Bot</span>
</div>
)}
<div className={"max-w-[80%] flex flex-col"}>
<div
className={`
px-4 py-3 rounded-2xl shadow-sm relative
${
message.sender === "user"
? "bg-blue-500 text-white rounded-tr-none"
: "bg-white text-gray-800 rounded-tl-none border border-gray-200"
}
`}
>
<p className="whitespace-pre-wrap">{message.text}</p>
</div>
{/* 复制按钮 */}
{message.sender === "bot" && (
<div className="flex gap-2 mt-2">
<Button
onPress={() => copyText(message.text, message.id)}
className= " px-1.5px py-1 rounded-sm font-medium hover:bg-blue-700 transition-colors text-xs w-4 h-4"
aria-label="复制消息"
>
</Button>
<Button
onPress={() => reasendMessage(message.id)}
className=" px-1.5px py-1 rounded-sm font-medium hover:bg-blue-700 transition-colors text-xs w-4 h-4"
aria-label="重新发送"
>
</Button>
</div>
)}
</div>
{/* {message.sender === "user" && (
<Button
onPress={() => reasendMessage(message.id)}
className=" px-4 py-2 rounded-sm font-medium hover:bg-blue-700 transition-colors text-sm w-fit"
aria-label="重新发送"
>
重新发送
</Button>
)}
</div> */}
{message.sender === "user" && (
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white flex-shrink-0 mr-2overflow-hidden">
<span className="text-xs font-bold">User</span>
</div>
)}
</div>
))}
{isBotTyping && (
<div className="flex justify-start items-start">
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white flex-shrink-0 mr-2">
<span className="text-xs font-bold">Bot</span>
</div>
<div className="bg-white border border-gray-200 rounded-2xl rounded-tl-none px-4 py-3 shadow-sm">
<div className="flex space-x-1">
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "0ms" }}
></div>
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "150ms" }}
></div>
<div
className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"
style={{ animationDelay: "300ms" }}
></div>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</main>
<div className="p-4 border-t border-gray-200 bg-gray-50">
<Button onPress={handleNewChat}
className="bg-blue-600 text-white px-4 py-2 rounded-sm font-medium hover:bg-blue-700 transition-colors text-sm w-fit"
> </Button>
<ChatbotInput
onSubmit={handleUserMessage}
placeholder="请输入要发送的消息"
minHeight={10}
maxHeight={80}
/>
</div>
</div>
);
};
export default ChatbotInterface;

14
src/pages/about.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { title } from "@/components/primitives";
import DefaultLayout from "@/layouts/default";
export default function DocsPage() {
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<h1 className={title()}>About</h1>
</div>
</section>
</DefaultLayout>
);
}

14
src/pages/blog.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { title } from "@/components/primitives";
import DefaultLayout from "@/layouts/default";
export default function DocsPage() {
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<h1 className={title()}>Blog</h1>
</div>
</section>
</DefaultLayout>
);
}

14
src/pages/docs.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { title } from "@/components/primitives";
import DefaultLayout from "@/layouts/default";
export default function DocsPage() {
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<h1 className={title()}>Docs</h1>
</div>
</section>
</DefaultLayout>
);
}

60
src/pages/index.tsx Normal file
View File

@@ -0,0 +1,60 @@
import { Link } from "@heroui/link";
import { Snippet } from "@heroui/snippet";
import { Code } from "@heroui/code";
import { button as buttonStyles } from "@heroui/theme";
import { siteConfig } from "@/config/site";
import { title, subtitle } from "@/components/primitives";
import { GithubIcon } from "@/components/icons";
import DefaultLayout from "@/layouts/default";
export default function IndexPage() {
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<span className={title()}>Make&nbsp;</span>
<span className={title({ color: "violet" })}>beautiful&nbsp;</span>
<br />
<span className={title()}>
websites regardless of your design experience.
</span>
<div className={subtitle({ class: "mt-4" })}>
Beautiful, fast and modern React UI library.
</div>
</div>
<div className="flex gap-3">
<Link
isExternal
className={buttonStyles({
color: "primary",
radius: "full",
variant: "shadow",
})}
href={siteConfig.links.docs}
>
Documentation
</Link>
<Link
isExternal
className={buttonStyles({ variant: "bordered", radius: "full" })}
href={siteConfig.links.github}
>
<GithubIcon size={20} />
GitHub
</Link>
</div>
<div className="mt-8">
<Snippet hideCopyButton hideSymbol variant="bordered">
<span>
Get started by editing{" "}
<Code color="primary">pages/index.tsx</Code>
</span>
</Snippet>
</div>
</section>
</DefaultLayout>
);
}

14
src/pages/pricing.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { title } from "@/components/primitives";
import DefaultLayout from "@/layouts/default";
export default function DocsPage() {
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<h1 className={title()}>Pricing</h1>
</div>
</section>
</DefaultLayout>
);
}

20
src/provider.tsx Normal file
View File

@@ -0,0 +1,20 @@
import type { NavigateOptions } from "react-router-dom";
import { HeroUIProvider } from "@heroui/system";
import { useHref, useNavigate } from "react-router-dom";
declare module "@react-types/shared" {
interface RouterConfig {
routerOptions: NavigateOptions;
}
}
export function Provider({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
return (
<HeroUIProvider navigate={navigate} useHref={useHref}>
{children}
</HeroUIProvider>
);
}

3
src/styles/globals.css Normal file
View File

@@ -0,0 +1,3 @@
@import "tailwindcss";
@config "../../tailwind.config.js"

5
src/types/index.ts Normal file
View File

@@ -0,0 +1,5 @@
import { SVGProps } from "react";
export type IconSvgProps = SVGProps<SVGSVGElement> & {
size?: number;
};

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

17
tailwind.config.js Normal file
View File

@@ -0,0 +1,17 @@
import {heroui} from "@heroui/theme"
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
'./src/layouts/**/*.{js,ts,jsx,tsx,mdx}',
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
darkMode: "class",
plugins: [heroui()],
}

28
tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
},
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
tsconfig.node.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

5
vercel.json Normal file
View File

@@ -0,0 +1,5 @@
{
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
]
}

9
vite.config.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
import tailwindcss from "@tailwindcss/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths(), tailwindcss()],
});

Binary file not shown.