update
This commit is contained in:
20
src/apps/auth.tsx
Normal file
20
src/apps/auth.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { queryLogin } from "@/modules/query"
|
||||
import { useState, useEffect } from "react";
|
||||
export const AuthProvider = (props: { children: React.ReactNode }) => {
|
||||
const [isLogin, setIsLogin] = useState<boolean>(false);
|
||||
const init = async () => {
|
||||
const token = await queryLogin.checkLocalToken();
|
||||
if (token) {
|
||||
console.log('User is logged in');
|
||||
setIsLogin(true);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
return (
|
||||
<div>
|
||||
{isLogin ? props.children : <div>Please log in to access this application.</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
70
src/apps/home/index.tsx
Normal file
70
src/apps/home/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { app } from '../ai';
|
||||
|
||||
|
||||
import { Sender, XProvider } from '@ant-design/x';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { Nav } from '../nav';
|
||||
|
||||
const useFocus = () => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useEffect(() => {
|
||||
// Focus the input element inside Sender component
|
||||
const focusInput = () => {
|
||||
if (inputRef.current) {
|
||||
const input = inputRef.current.querySelector('input, textarea') as HTMLInputElement | HTMLTextAreaElement;
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Focus on mount
|
||||
focusInput();
|
||||
|
||||
// Also focus after a short delay to ensure everything is rendered
|
||||
const timeoutId = setTimeout(focusInput, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, []);
|
||||
|
||||
return inputRef;
|
||||
}
|
||||
|
||||
|
||||
export const App = () => {
|
||||
const inputRef = useFocus();
|
||||
|
||||
|
||||
|
||||
return <div className='container mx-auto p-4'>
|
||||
<div className='fixed bottom-8 w-1/2 justify-self-center' ref={inputRef}>
|
||||
<Sender allowSpeech onSubmit={() => {
|
||||
console.log('Submitted');
|
||||
}} />
|
||||
</div>
|
||||
</div >;
|
||||
}
|
||||
|
||||
|
||||
export const AppProvider = () => {
|
||||
return <XProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: '#000000',
|
||||
colorBgBase: '#ffffff',
|
||||
colorTextBase: '#000000',
|
||||
colorBorder: '#d9d9d9',
|
||||
colorBgContainer: '#ffffff',
|
||||
colorBgElevated: '#ffffff',
|
||||
colorBgLayout: '#ffffff',
|
||||
colorText: '#000000',
|
||||
colorTextSecondary: '#666666',
|
||||
colorTextTertiary: '#999999',
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Nav />
|
||||
<App />
|
||||
</XProvider>;
|
||||
}
|
||||
80
src/apps/nav/index.tsx
Normal file
80
src/apps/nav/index.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUserStore } from "./store.ts";
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import '@kevisual/kv-login';
|
||||
import { useContextKey } from "@kevisual/context";
|
||||
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components/ui/dialog";
|
||||
|
||||
export const LoginComponent = ({ onLoginSuccess }: { onLoginSuccess: () => void }) => {
|
||||
useEffect(() => {
|
||||
// 监听登录成功事件
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('监听到登录成功事件,关闭弹窗');
|
||||
onLoginSuccess();
|
||||
};
|
||||
const loginEmitter = useContextKey('login-emitter')
|
||||
console.log('KvLogin Types:', loginEmitter);
|
||||
|
||||
loginEmitter.on('login-success', handleLoginSuccess);
|
||||
|
||||
// 清理监听器
|
||||
return () => {
|
||||
loginEmitter.off('login-success', handleLoginSuccess);
|
||||
};
|
||||
}, [onLoginSuccess]);
|
||||
|
||||
// @ts-ignore
|
||||
return (<kv-login><div id="weixinLogin"></div></kv-login>)
|
||||
}
|
||||
export const Nav = () => {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const store = useUserStore(useShallow((state) => ({
|
||||
user: state.user,
|
||||
setUser: state.setUser,
|
||||
clearUser: state.clearUser,
|
||||
queryUser: state.queryUser
|
||||
})));
|
||||
|
||||
useEffect(() => {
|
||||
store.queryUser();
|
||||
}, []);
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
// 关闭弹窗
|
||||
setIsDialogOpen(false);
|
||||
// 重新查询用户信息
|
||||
};
|
||||
return <header>
|
||||
<nav className="bg-black p-4 text-white flex justify-between">
|
||||
<div className="text-lg font-bold">人生可视化助手</div>
|
||||
<div>
|
||||
{store.user ? (
|
||||
<div className="flex items-center space-x-4">
|
||||
{store.user.avatar && <img src={store.user.avatar} alt="Avatar" className="w-8 h-8 rounded-full" />}
|
||||
<span>{store.user.username}</span>
|
||||
<button
|
||||
onClick={() => store.clearUser()}
|
||||
className="bg-gray-700 text-white px-3 py-1 rounded hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<button className="bg-gray-700 text-white px-3 py-1 rounded hover:bg-gray-600 transition-colors">
|
||||
登录
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader className="text-black text-xl font-bold border-b border-black pb-3">登录</DialogHeader>
|
||||
<LoginComponent onLoginSuccess={handleLoginSuccess} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
}
|
||||
33
src/apps/nav/store.ts
Normal file
33
src/apps/nav/store.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { queryLogin } from '@/modules/query';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface UserState {
|
||||
user: {
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
id?: string;
|
||||
needChangePassword?: boolean;
|
||||
orgs?: string[];
|
||||
type?: string;
|
||||
username?: string;
|
||||
} | null;
|
||||
setUser: (user: UserState['user']) => void;
|
||||
clearUser: () => void;
|
||||
queryUser: () => void;
|
||||
queryMe: () => void;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserState>((set) => ({
|
||||
user: null,
|
||||
setUser: (user) => set({ user }),
|
||||
clearUser: () => set({ user: null }),
|
||||
queryUser: async () => {
|
||||
const user = await queryLogin.checkLocalUser();
|
||||
set({ user });
|
||||
},
|
||||
queryMe: async () => {
|
||||
const user = await queryLogin.getMe();
|
||||
set({ user });
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { app } from '../ai';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { local } from '@/modules/query';
|
||||
import '@kevisual/kv-login'
|
||||
import { AuthProvider } from '../auth';
|
||||
|
||||
const getAppRoutes = () => {
|
||||
const appRoutes = app.routes.map((route) => {
|
||||
return {
|
||||
@@ -14,12 +15,6 @@ const getAppRoutes = () => {
|
||||
return appRoutes;
|
||||
}
|
||||
|
||||
const fetchTest = async () => {
|
||||
const url = 'http://localhost:51015/api/router';
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
console.log('Fetch Test Data:', data);
|
||||
}
|
||||
const fetchLocal = async () => {
|
||||
const res = await local.post({
|
||||
path: 'client',
|
||||
@@ -34,6 +29,7 @@ const dynamicImport = async () => {
|
||||
console.log('Test Function Output:', module.test());
|
||||
|
||||
}
|
||||
|
||||
export const App = () => {
|
||||
const [appRoutes, setAppRoutes] = useState(getAppRoutes());
|
||||
|
||||
@@ -53,18 +49,12 @@ export const App = () => {
|
||||
setAppRoutes(getAppRoutes());
|
||||
}
|
||||
}>{JSON.stringify(appRoutes, null, 2)}</pre>
|
||||
|
||||
<kv-login>
|
||||
<div id="weixinLogin"></div>
|
||||
</kv-login>
|
||||
</div >;
|
||||
}
|
||||
|
||||
// Add custom element to JSX namespace for TypeScript
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'kv-login': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||
}
|
||||
}
|
||||
|
||||
export const AppProvider = () => {
|
||||
return <AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
}
|
||||
145
src/components/ui/dialog.tsx
Normal file
145
src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { VisuallyHidden } from "./visually-hidden"
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||
showCloseButton?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<VisuallyHidden>
|
||||
<DialogPrimitive.Title>Dialog</DialogPrimitive.Title>
|
||||
</VisuallyHidden>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close
|
||||
data-slot="dialog-close"
|
||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
168
src/components/ui/navigation-menu.tsx
Normal file
168
src/components/ui/navigation-menu.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import * as React from "react"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function NavigationMenu({
|
||||
className,
|
||||
children,
|
||||
viewport = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
||||
viewport?: boolean
|
||||
}) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Root
|
||||
data-slot="navigation-menu"
|
||||
data-viewport={viewport}
|
||||
className={cn(
|
||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{viewport && <NavigationMenuViewport />}
|
||||
</NavigationMenuPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.List
|
||||
data-slot="navigation-menu-list"
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center gap-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Item
|
||||
data-slot="navigation-menu-item"
|
||||
className={cn("relative", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
)
|
||||
|
||||
function NavigationMenuTrigger({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
data-slot="navigation-menu-trigger"
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDownIcon
|
||||
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Content
|
||||
data-slot="navigation-menu-content"
|
||||
className={cn(
|
||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuViewport({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-full left-0 isolate z-50 flex justify-center"
|
||||
)}
|
||||
>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
data-slot="navigation-menu-viewport"
|
||||
className={cn(
|
||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuLink({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Link
|
||||
data-slot="navigation-menu-link"
|
||||
className={cn(
|
||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationMenuIndicator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
||||
return (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
data-slot="navigation-menu-indicator"
|
||||
className={cn(
|
||||
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
navigationMenuTriggerStyle,
|
||||
}
|
||||
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 }
|
||||
16
src/components/ui/visually-hidden.tsx
Normal file
16
src/components/ui/visually-hidden.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function VisuallyHidden({ className, ...props }: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"absolute h-px w-px p-0 -m-px overflow-hidden whitespace-nowrap border-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { VisuallyHidden }
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Query } from "@kevisual/query";
|
||||
|
||||
import { QueryLoginBrowser } from '@kevisual/query-login';
|
||||
|
||||
export const query = new Query();
|
||||
|
||||
export const queryLogin = new QueryLoginBrowser({
|
||||
query
|
||||
})
|
||||
export const local = new Query({
|
||||
url: '/client/router'
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
import Html from '@/components/html.astro';
|
||||
import { App } from '@/apps/web-command';
|
||||
import { AppProvider } from '@/apps/home';
|
||||
---
|
||||
|
||||
<Html>
|
||||
<Html title='可视化平台'>
|
||||
<main>
|
||||
<App client:only/>
|
||||
<AppProvider client:only />
|
||||
</main>
|
||||
</Html>
|
||||
|
||||
Reference in New Issue
Block a user