mirror of
https://github.com/coaidev/coai.git
synced 2025-05-21 05:50:14 +09:00
feat: optimize form scrollbars
This commit is contained in:
parent
9087779a96
commit
c993fe34e2
@ -82,4 +82,8 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
background: hsla(var(--background-container));
|
background: hsla(var(--background-container));
|
||||||
|
|
||||||
|
& > .scrollarea-viewport > div {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -636,14 +636,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-content {
|
.chat-content {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
touch-action: pan-y;
|
touch-action: pan-y;
|
||||||
padding: 18px;
|
|
||||||
|
.chat-messages-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 18px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
@ -106,6 +106,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.horizontal-scrollbar {
|
||||||
|
--radix-scroll-area-thumb-height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
-webkit-appearance: textfield;
|
-webkit-appearance: textfield;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -419,7 +419,9 @@ function UserTable() {
|
|||||||
<TableCell>{user.quota}</TableCell>
|
<TableCell>{user.quota}</TableCell>
|
||||||
<TableCell>{user.used_quota}</TableCell>
|
<TableCell>{user.used_quota}</TableCell>
|
||||||
<TableCell>{t(user.is_subscribed.toString())}</TableCell>
|
<TableCell>{t(user.is_subscribed.toString())}</TableCell>
|
||||||
<TableCell className={`whitespace-nowrap`}>{t(`admin.identity.${userTypeArray[user.level]}`)}</TableCell>
|
<TableCell className={`whitespace-nowrap`}>
|
||||||
|
{t(`admin.identity.${userTypeArray[user.level]}`)}
|
||||||
|
</TableCell>
|
||||||
<TableCell>{user.total_month}</TableCell>
|
<TableCell>{user.total_month}</TableCell>
|
||||||
{useDeeptrain && (
|
{useDeeptrain && (
|
||||||
<TableCell>{t(user.enterprise.toString())}</TableCell>
|
<TableCell>{t(user.enterprise.toString())}</TableCell>
|
||||||
|
@ -36,20 +36,22 @@ function ChatInterface({ scrollable, setTarget }: ChatInterfaceProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className={`chat-content`} ref={ref}>
|
<ScrollArea className={`chat-content`} ref={ref}>
|
||||||
{messages.map((message, i) => (
|
<div className={`chat-messages-wrapper`}>
|
||||||
<MessageSegment
|
{messages.map((message, i) => (
|
||||||
message={message}
|
<MessageSegment
|
||||||
end={i === messages.length - 1}
|
message={message}
|
||||||
onEvent={(event: string, index?: number, message?: string) => {
|
end={i === messages.length - 1}
|
||||||
process({ id: current, event, index, message });
|
onEvent={(event: string, index?: number, message?: string) => {
|
||||||
}}
|
process({ id: current, event, index, message });
|
||||||
key={i}
|
}}
|
||||||
index={i}
|
key={i}
|
||||||
selected={selected === i}
|
index={i}
|
||||||
onFocus={() => setSelected(i)}
|
selected={selected === i}
|
||||||
onFocusLeave={() => setSelected(-1)}
|
onFocus={() => setSelected(i)}
|
||||||
/>
|
onFocusLeave={() => setSelected(-1)}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
117
app/src/components/ui/pagination.tsx
Normal file
117
app/src/components/ui/pagination.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/components/ui/lib/utils";
|
||||||
|
import { ButtonProps, buttonVariants } from "src/components/ui/button";
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Pagination.displayName = "Pagination";
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
PaginationContent.displayName = "PaginationContent";
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
));
|
||||||
|
PaginationItem.displayName = "PaginationItem";
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean;
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">;
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
PaginationLink.displayName = "PaginationLink";
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious";
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationNext.displayName = "PaginationNext";
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
};
|
@ -13,7 +13,7 @@ const ScrollArea = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport
|
<ScrollAreaPrimitive.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="h-full w-full rounded-[inherit]"
|
className="scrollarea-viewport h-full w-full rounded-[inherit]"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
@ -35,7 +35,7 @@ const ScrollBar = React.forwardRef<
|
|||||||
orientation === "vertical" &&
|
orientation === "vertical" &&
|
||||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
"horizontal-scrollbar h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "./lib/utils.ts";
|
import { cn } from "./lib/utils.ts";
|
||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area.tsx";
|
||||||
|
|
||||||
type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
||||||
classNameWrapper?: string;
|
classNameWrapper?: string;
|
||||||
@ -8,13 +9,16 @@ type TableProps = React.HTMLAttributes<HTMLTableElement> & {
|
|||||||
|
|
||||||
const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
||||||
({ className, classNameWrapper, ...props }, ref) => (
|
({ className, classNameWrapper, ...props }, ref) => (
|
||||||
<div className={cn("relative w-full overflow-auto", classNameWrapper)}>
|
<ScrollArea type="always">
|
||||||
<table
|
<div className={cn("relative w-full mb-2", classNameWrapper)}>
|
||||||
ref={ref}
|
<table
|
||||||
className={cn("w-full caption-bottom text-sm", className)}
|
ref={ref}
|
||||||
{...props}
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
/>
|
{...props}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollBar className="cursor-pointer" orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Table.displayName = "Table";
|
Table.displayName = "Table";
|
||||||
|
@ -244,7 +244,7 @@
|
|||||||
"download": "Download {{name}} format"
|
"download": "Download {{name}} format"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"title": "API Settings",
|
"title": "API Key",
|
||||||
"copied": "Copied",
|
"copied": "Copied",
|
||||||
"copied-description": "API key has been copied to clipboard",
|
"copied-description": "API key has been copied to clipboard",
|
||||||
"learn-more": "Learn more",
|
"learn-more": "Learn more",
|
||||||
@ -273,7 +273,7 @@
|
|||||||
"copied-description": "Link has been copied to clipboard",
|
"copied-description": "Link has been copied to clipboard",
|
||||||
"not-found": "Conversation not found",
|
"not-found": "Conversation not found",
|
||||||
"not-found-description": "Conversation not found, please check if the link is correct or the conversation has been deleted",
|
"not-found-description": "Conversation not found, please check if the link is correct or the conversation has been deleted",
|
||||||
"manage": "Share Management",
|
"manage": "Sharing",
|
||||||
"sync-error": "Sync Error",
|
"sync-error": "Sync Error",
|
||||||
"name": "Conversation Title",
|
"name": "Conversation Title",
|
||||||
"time": "Time",
|
"time": "Time",
|
||||||
@ -290,7 +290,7 @@
|
|||||||
"check-success": "Redeem Success",
|
"check-success": "Redeem Success",
|
||||||
"check-success-description": "Redeem Success! You have received {{amount}} points, start your AI journey!",
|
"check-success-description": "Redeem Success! You have received {{amount}} points, start your AI journey!",
|
||||||
"check-failed": "Redeem Failed",
|
"check-failed": "Redeem Failed",
|
||||||
"invitation": "Invitation Code"
|
"invitation": "Gift"
|
||||||
},
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"title": "Contact Us"
|
"title": "Contact Us"
|
||||||
|
@ -5,6 +5,7 @@ import { useSelector } from "react-redux";
|
|||||||
import { selectAdmin, selectInit } from "@/store/auth.ts";
|
import { selectAdmin, selectInit } from "@/store/auth.ts";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import router from "@/router.tsx";
|
import router from "@/router.tsx";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area.tsx";
|
||||||
|
|
||||||
function Admin() {
|
function Admin() {
|
||||||
const init = useSelector(selectInit);
|
const init = useSelector(selectInit);
|
||||||
@ -17,9 +18,9 @@ function Admin() {
|
|||||||
return (
|
return (
|
||||||
<div className={`admin-page`}>
|
<div className={`admin-page`}>
|
||||||
<MenuBar />
|
<MenuBar />
|
||||||
<div className={`admin-content thin-scrollbar`}>
|
<ScrollArea className={`admin-content`}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -126,9 +126,11 @@ function SharingForm({ refer, data }: SharingFormProps) {
|
|||||||
<div className={`time`}>{time}</div>
|
<div className={`time`}>{time}</div>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className={`body`}>
|
<ScrollArea className={`body`}>
|
||||||
{data.messages.map((message, i) => (
|
<div className={`chat-messages-wrapper`}>
|
||||||
<MessageSegment message={message} key={i} index={i} />
|
{data.messages.map((message, i) => (
|
||||||
))}
|
<MessageSegment message={message} key={i} index={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<div className={`action`}>
|
<div className={`action`}>
|
||||||
<Button
|
<Button
|
||||||
|
Loading…
Reference in New Issue
Block a user