Merge pull request #1605 from TwperBody/master

feat: dark mode supports for webui
This commit is contained in:
Junyan Qin (Chin)
2025-08-11 20:51:58 +08:00
committed by GitHub
39 changed files with 493 additions and 200 deletions

View File

@@ -56,6 +56,15 @@
background: rgba(0, 0, 0, 0.35); /* 悬停加深 */
}
/* 暗黑模式下的滚动条 */
.dark ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2); /* 半透明白色 */
}
.dark ::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.35); /* 悬停加深 */
}
/* 兼容 Edge */
@supports (-ms-ime-align: auto) {
body {
@@ -108,36 +117,36 @@
}
.dark {
--background: oklch(0.141 0.005 285.823);
--background: oklch(0.08 0.002 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card: oklch(0.12 0.004 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover: oklch(0.12 0.004 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--primary: oklch(0.62 0.2 255);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.18 0.004 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted: oklch(0.18 0.004 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent: oklch(0.18 0.004 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--border: oklch(1 0 0 / 8%);
--input: oklch(1 0 0 / 10%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar: oklch(0.1 0.003 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-primary: oklch(0.62 0.2 255);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.18 0.004 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: oklch(1 0 0 / 8%);
--sidebar-ring: oklch(0.552 0.016 285.938);
}

View File

@@ -159,7 +159,7 @@ export default function BotDetailDialog({
<SidebarProvider className="items-start w-full flex">
<Sidebar
collapsible="none"
className="hidden md:flex h-[80vh] w-40 min-w-[120px] border-r bg-white"
className="hidden md:flex h-[80vh] w-40 min-w-[120px] border-r bg-white dark:bg-black"
>
<SidebarContent>
<SidebarGroup>

View File

@@ -6,12 +6,22 @@
box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.2);
padding: 1.2rem;
cursor: pointer;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.iconBasicInfoContainer {
width: 100%;
height: 100%;
@@ -47,6 +57,11 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #1a1a1a;
}
:global(.dark) .basicInfoName {
color: #f0f0f0;
}
.basicInfoDescription {
@@ -58,6 +73,10 @@
text-overflow: ellipsis;
}
:global(.dark) .basicInfoDescription {
color: #888888;
}
.basicInfoAdapterContainer {
display: flex;
flex-direction: row;
@@ -71,12 +90,20 @@
color: #626262;
}
:global(.dark) .basicInfoAdapterIcon {
color: #a0a0a0;
}
.basicInfoAdapterLabel {
font-size: 1.2rem;
font-weight: 500;
color: #626262;
}
:global(.dark) .basicInfoAdapterLabel {
color: #a0a0a0;
}
.basicInfoPipelineContainer {
display: flex;
flex-direction: row;
@@ -90,12 +117,20 @@
margin-top: 0.2rem;
}
:global(.dark) .basicInfoPipelineIcon {
color: #a0a0a0;
}
.basicInfoPipelineLabel {
font-size: 1.2rem;
font-weight: 500;
color: #626262;
}
:global(.dark) .basicInfoPipelineLabel {
color: #a0a0a0;
}
.bigText {
white-space: nowrap;
overflow: hidden;

View File

@@ -394,7 +394,7 @@ export default function BotForm({
<FormLabel>{t('bots.bindPipeline')}</FormLabel>
<FormControl>
<Select onValueChange={field.onChange} {...field}>
<SelectTrigger>
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue
placeholder={t('bots.selectPipeline')}
/>
@@ -467,7 +467,7 @@ export default function BotForm({
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectTrigger className="w-[180px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('bots.selectAdapter')} />
</SelectTrigger>
<SelectContent className="fixed z-[1000]">

View File

@@ -18,6 +18,11 @@
cursor: pointer;
}
:global(.dark) .botLogCardContainer {
background-color: #1f1f22;
border: 1px solid #2a2a2e;
}
.listHeader {
width: 100%;
height: 2.5rem;

View File

@@ -132,7 +132,7 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.SELECT:
return (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('common.select')} />
</SelectTrigger>
<SelectContent>
@@ -150,7 +150,7 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.LLM_MODEL_SELECTOR:
return (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('models.selectModel')} />
</SelectTrigger>
<SelectContent>
@@ -267,7 +267,7 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.KNOWLEDGE_BASE_SELECTOR:
return (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('knowledge.selectKnowledgeBase')} />
</SelectTrigger>
<SelectContent>
@@ -291,7 +291,7 @@ export default function DynamicFormItemComponent({
<div key={index} className="flex gap-2 items-center">
{/* 角色选择 */}
{index === 0 ? (
<div className="w-[120px] px-3 py-2 border rounded bg-gray-50 text-gray-500">
<div className="w-[120px] px-3 py-2 border rounded bg-gray-50 dark:bg-[#2a292e] text-gray-500 dark:text-white dark:border-gray-600">
system
</div>
) : (
@@ -303,7 +303,7 @@ export default function DynamicFormItemComponent({
field.onChange(newValue);
}}
>
<SelectTrigger className="w-[120px]">
<SelectTrigger className="w-[120px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@@ -1,27 +0,0 @@
import styles from './emptyAndCreate.module.css';
export default function EmptyAndCreateComponent({
title,
subTitle,
buttonText,
onButtonClick,
}: {
title: string;
subTitle: string;
buttonText: string;
onButtonClick: () => void;
}) {
return (
<div className={`${styles.emptyPageContainer}`}>
<div className={`${styles.emptyContainer}`}>
<div className={`${styles.emptyInfoContainer}`}>
<div className={`${styles.emptyInfoText}`}>{title}</div>
<div className={`${styles.emptyInfoSubText}`}>{subTitle}</div>
</div>
<div className={`${styles.emptyCreateButton}`} onClick={onButtonClick}>
{buttonText}
</div>
</div>
</div>
);
}

View File

@@ -1,54 +0,0 @@
.emptyPageContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #fff;
border: 1px solid #c5c5c5;
border-radius: 10px;
}
.emptyContainer {
width: 100%;
height: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
}
.emptyCreateButton {
width: 200px;
height: 50px;
border-radius: 20px;
background-color: #2288ee;
color: #fff;
font-size: 20px;
font-weight: bold;
text-align: center;
line-height: 50px;
user-select: none;
}
.emptyCreateButton:hover {
background-color: #1b77d2;
}
.emptyInfoContainer {
width: 100%;
height: 60px;
display: flex;
flex-direction: column;
align-items: center;
color: #353535;
}
.emptyInfoText {
font-size: 30px;
}
.emptyInfoSubText {
font-size: 28px;
}

View File

@@ -13,6 +13,10 @@
/* box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1); */
}
:global(.dark) .sidebarContainer {
background-color: #0a0a0b !important;
}
.langbotIconContainer {
width: 200px;
height: 70px;
@@ -21,32 +25,49 @@
align-items: center;
justify-content: center;
gap: 0.8rem;
}
.langbotIcon {
width: 2.8rem;
height: 2.8rem;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.langbotIcon {
width: 2.8rem;
height: 2.8rem;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.langbotTextContainer {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 0.1rem;
}
:global(.dark) .langbotIcon {
box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.1);
}
.langbotText {
font-size: 1.4rem;
font-weight: 500;
}
.langbotTextContainer {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 0.1rem;
}
.langbotVersion {
font-size: 0.8rem;
font-weight: 700;
color: #6c6c6c;
}
.langbotText {
font-size: 1.4rem;
font-weight: 500;
color: #1a1a1a;
}
:global(.dark) .langbotText {
font-size: 1.4rem;
font-weight: 500;
color: #f0f0f0 !important;
}
.langbotVersion {
font-size: 0.8rem;
font-weight: 700;
color: #6c6c6c;
}
:global(.dark) .langbotVersion {
font-size: 0.8rem;
font-weight: 700;
color: #a0a0a0 !important;
}
.sidebarTopContainer {
@@ -76,6 +97,7 @@
justify-content: flex-start;
cursor: pointer;
gap: 0.5rem;
transition: all 0.2s ease;
/* background-color: aqua; */
}
@@ -85,16 +107,40 @@
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .sidebarSelected {
background-color: #2288ee;
color: white;
box-shadow: 0 0 10px 0 rgba(34, 136, 238, 0.3);
}
.sidebarUnselected {
color: #6c6c6c;
}
:global(.dark) .sidebarUnselected {
color: #a0a0a0 !important;
}
.sidebarUnselected:hover {
background-color: rgba(34, 136, 238, 0.1);
color: #2288ee;
}
:global(.dark) .sidebarUnselected:hover {
background-color: rgba(34, 136, 238, 0.2);
color: #66baff;
}
.sidebarChildIcon {
width: 20px;
height: 20px;
background-color: rgba(96, 149, 209, 0);
}
.sidebarChildName {
color: inherit;
}
.sidebarBottomContainer {
width: 100%;
display: flex;

View File

@@ -17,6 +17,10 @@
color: #585858;
}
:global(.dark) .titleText {
color: #e0e0e0;
}
.subtitleText {
margin-left: 3.2rem;
font-size: 0.8rem;
@@ -25,8 +29,16 @@
align-items: center;
}
:global(.dark) .subtitleText {
color: #b0b0b0;
}
.helpLink {
margin-left: 0.2rem;
font-size: 0.8rem;
color: #8b8b8b;
}
:global(.dark) .helpLink {
color: #a0a0a0;
}

View File

@@ -152,7 +152,7 @@ export default function KBDetailDialog({
<SidebarProvider className="items-start w-full flex">
<Sidebar
collapsible="none"
className="hidden md:flex h-[80vh] w-40 min-w-[120px] border-r bg-white"
className="hidden md:flex h-[80vh] w-40 min-w-[120px] border-r bg-white dark:bg-black"
>
<SidebarContent>
<SidebarGroup>

View File

@@ -10,12 +10,22 @@
flex-direction: row;
justify-content: space-between;
gap: 0.5rem;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.basicInfoContainer {
width: 100%;
height: 100%;
@@ -35,6 +45,11 @@
.basicInfoNameText {
font-size: 1.4rem;
font-weight: 500;
color: #1a1a1a;
}
:global(.dark) .basicInfoNameText {
color: #f0f0f0;
}
.basicInfoDescriptionText {
@@ -48,6 +63,10 @@
color: #b1b1b1;
}
:global(.dark) .basicInfoDescriptionText {
color: #888888;
}
.basicInfoLastUpdatedTimeContainer {
display: flex;
flex-direction: row;
@@ -58,11 +77,21 @@
.basicInfoUpdateTimeIcon {
width: 1.2rem;
height: 1.2rem;
color: #626262;
}
:global(.dark) .basicInfoUpdateTimeIcon {
color: #a0a0a0;
}
.basicInfoUpdateTimeText {
font-size: 1rem;
font-weight: 400;
color: #626262;
}
:global(.dark) .basicInfoUpdateTimeText {
color: #a0a0a0;
}
.operationContainer {
@@ -86,12 +115,20 @@
color: #ffcd27;
}
:global(.dark) .operationDefaultBadgeIcon {
color: #fbbf24;
}
.operationDefaultBadgeText {
font-size: 1rem;
font-weight: 400;
color: #ffcd27;
}
:global(.dark) .operationDefaultBadgeText {
color: #fbbf24;
}
.bigText {
white-space: nowrap;
overflow: hidden;

View File

@@ -127,12 +127,12 @@ export default function FileUploadZone({
</div>
<div>
<p className="text-base font-medium text-gray-900">
<p className="text-base font-medium text-gray-900 dark:text-gray-100">
{isUploading
? t('knowledge.documentsTab.uploading')
: t('knowledge.documentsTab.dragAndDrop')}
</p>
<p className="text-xs text-gray-500 mt-1">
<p className="text-xs text-gray-500 mt-1 dark:text-gray-400">
{t('knowledge.documentsTab.supportedFormats')}
</p>
</div>

View File

@@ -77,7 +77,10 @@ export const columns = (
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuContent
align="end"
className="bg-white dark:bg-[#2a2a2e]"
>
<DropdownMenuLabel>
{t('knowledge.documentsTab.actions')}
</DropdownMenuLabel>

View File

@@ -200,7 +200,7 @@ export default function KBForm({
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectTrigger className="w-[180px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue
placeholder={t('knowledge.selectEmbeddingModel')}
/>

View File

@@ -7,6 +7,19 @@
background-color: #eee;
}
:global(.dark) .homeLayoutContainer {
background-color: #0a0a0b;
}
/* 侧边栏区域 */
.sidebar {
background-color: #eee;
}
:global(.dark) .sidebar {
background-color: #0a0a0b;
}
/* 主内容区域 */
.main {
background-color: #fafafa;
@@ -23,6 +36,11 @@
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.05);
}
:global(.dark) .main {
background-color: #151518;
box-shadow: 0 0 6px 0 rgba(255, 255, 255, 0.05);
}
.mainContent {
padding: 1.5rem;
padding-left: 2rem;
@@ -30,3 +48,7 @@
overflow-y: auto;
background-color: #fafafa;
}
:global(.dark) .mainContent {
background-color: #151518;
}

View File

@@ -6,12 +6,22 @@
box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.2);
padding: 1.2rem;
cursor: pointer;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.iconBasicInfoContainer {
width: 100%;
height: 100%;
@@ -39,6 +49,11 @@
.basicInfoText {
font-size: 1.4rem;
font-weight: bold;
color: #1a1a1a;
}
:global(.dark) .basicInfoText {
color: #f0f0f0;
}
.providerContainer {
@@ -56,12 +71,20 @@
color: #626262;
}
:global(.dark) .providerIcon {
color: #a0a0a0;
}
.providerLabel {
font-size: 1.2rem;
font-weight: 600;
color: #626262;
}
:global(.dark) .providerLabel {
color: #a0a0a0;
}
.baseURLContainer {
display: flex;
flex-direction: row;
@@ -77,6 +100,10 @@
color: #626262;
}
:global(.dark) .baseURLIcon {
color: #a0a0a0;
}
.baseURLText {
font-size: 1rem;
width: 100%;
@@ -87,6 +114,10 @@
max-width: 100%;
}
:global(.dark) .baseURLText {
color: #a0a0a0;
}
.bigText {
white-space: nowrap;
overflow: hidden;

View File

@@ -404,7 +404,7 @@ export default function EmbeddingForm({
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectTrigger className="w-[180px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue
placeholder={t('models.selectModelProvider')}
/>
@@ -479,7 +479,7 @@ export default function EmbeddingForm({
updateExtraArg(index, 'type', value)
}
>
<SelectTrigger className="w-[120px]">
<SelectTrigger className="w-[120px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('models.type')} />
</SelectTrigger>
<SelectContent>

View File

@@ -6,12 +6,22 @@
box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.2);
padding: 1.2rem;
cursor: pointer;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.iconBasicInfoContainer {
width: 100%;
height: 100%;
@@ -40,6 +50,11 @@
.basicInfoText {
font-size: 1.4rem;
font-weight: bold;
color: #1a1a1a;
}
:global(.dark) .basicInfoText {
color: #f0f0f0;
}
.providerContainer {
@@ -57,12 +72,20 @@
color: #626262;
}
:global(.dark) .providerIcon {
color: #a0a0a0;
}
.providerLabel {
font-size: 1.2rem;
font-weight: 600;
color: #626262;
}
:global(.dark) .providerLabel {
color: #a0a0a0;
}
.baseURLContainer {
display: flex;
flex-direction: row;
@@ -78,6 +101,10 @@
color: #626262;
}
:global(.dark) .baseURLIcon {
color: #a0a0a0;
}
.baseURLText {
font-size: 1rem;
width: 100%;
@@ -88,6 +115,10 @@
max-width: 100%;
}
:global(.dark) .baseURLText {
color: #a0a0a0;
}
.abilitiesContainer {
display: flex;
flex-direction: row;
@@ -108,18 +139,30 @@
background-color: #66baff80;
}
:global(.dark) .abilityBadge {
background-color: rgba(34, 136, 238, 0.3);
}
.abilityIcon {
width: 1rem;
height: 1rem;
color: #2288ee;
}
:global(.dark) .abilityIcon {
color: #66baff;
}
.abilityLabel {
font-size: 0.8rem;
font-weight: 400;
color: #2288ee;
}
:global(.dark) .abilityLabel {
color: #66baff;
}
.bigText {
white-space: nowrap;
overflow: hidden;

View File

@@ -420,7 +420,7 @@ export default function LLMForm({
}}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectTrigger className="w-[180px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue
placeholder={t('models.selectModelProvider')}
/>
@@ -553,7 +553,7 @@ export default function LLMForm({
updateExtraArg(index, 'type', value)
}
>
<SelectTrigger className="w-[120px]">
<SelectTrigger className="w-[120px] bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('models.type')} />
</SelectTrigger>
<SelectContent>

View File

@@ -192,7 +192,7 @@ export default function LLMConfigPage() {
<Tabs defaultValue="llm" className="w-full">
<div className="flex flex-row gap-0 mb-4">
<div className="flex flex-row justify-between items-center px-[0.8rem]">
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
<TabsList className="shadow-md py-5 bg-[#f0f0f0] dark:bg-[#2a2a2e]">
<TabsTrigger value="llm" className="px-6 py-4 cursor-pointer">
{t('llm.llmModels')}
</TabsTrigger>
@@ -206,12 +206,14 @@ export default function LLMConfigPage() {
</div>
<TabsContent value="llm">
<div className="flex flex-row justify-between items-center px-[0.4rem] h-full">
<p className="text-sm text-gray-500">{t('llm.description')}</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
{t('llm.description')}
</p>
</div>
</TabsContent>
<TabsContent value="embedding">
<div className="flex flex-row justify-between items-center px-[0.4rem] h-full">
<p className="text-sm text-gray-500">
<p className="text-sm text-gray-500 dark:text-gray-400">
{t('embedding.description')}
</p>
</div>

View File

@@ -142,7 +142,7 @@ export default function PipelineDialog({
<SidebarProvider className="items-start w-full flex h-full min-h-0">
<Sidebar
collapsible="none"
className="hidden md:flex h-full min-h-0 w-40 border-r bg-white"
className="hidden md:flex h-full min-h-0 w-40 border-r bg-white dark:bg-black"
>
<SidebarContent>
<SidebarGroup>

View File

@@ -15,13 +15,13 @@ export default function AtBadge({
return (
<Badge
variant="secondary"
className="flex items-center gap-1 px-2 py-1 text-sm bg-blue-100 text-blue-600 hover:bg-blue-200"
className="flex items-center gap-1 px-2 py-1 text-sm bg-blue-100 dark:bg-blue-900/40 text-blue-600 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-900/60"
>
@{targetName}
{!readonly && onRemove && (
<button
onClick={onRemove}
className="ml-1 hover:text-blue-800 focus:outline-none"
className="ml-1 hover:text-blue-800 dark:hover:text-blue-200 focus:outline-none"
>
<X className="h-3 w-3" />
</button>

View File

@@ -218,14 +218,14 @@ export default function DebugDialog({
const renderContent = () => (
<div className="flex flex-1 h-full min-h-0">
<div className="w-14 bg-white p-2 pl-0 flex-shrink-0 flex flex-col justify-start gap-2">
<div className="w-14 bg-white dark:bg-black p-2 pl-0 flex-shrink-0 flex flex-col justify-start gap-2">
<Button
variant="ghost"
size="icon"
className={`w-10 h-10 justify-center rounded-md transition-none ${
sessionType === 'person'
? 'bg-[#2288ee] text-white hover:bg-[#2288ee] hover:text-white'
: 'bg-white text-gray-800 hover:bg-gray-100'
: 'bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'
} border-0 shadow-none`}
onClick={() => setSessionType('person')}
>
@@ -244,7 +244,7 @@ export default function DebugDialog({
className={`w-10 h-10 justify-center rounded-md transition-none ${
sessionType === 'group'
? 'bg-[#2288ee] text-white hover:bg-[#2288ee] hover:text-white'
: 'bg-white text-gray-800 hover:bg-gray-100'
: 'bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700'
} border-0 shadow-none`}
onClick={() => setSessionType('group')}
>
@@ -261,7 +261,7 @@ export default function DebugDialog({
</div>
<div className="flex-1 flex flex-col w-[10rem] h-full min-h-0">
<ScrollArea className="flex-1 p-6 overflow-y-auto min-h-0 bg-white">
<ScrollArea className="flex-1 p-6 overflow-y-auto min-h-0 bg-white dark:bg-black">
<div className="space-y-6">
{messages.length === 0 ? (
<div className="text-center text-muted-foreground py-12 text-lg">
@@ -281,7 +281,7 @@ export default function DebugDialog({
'max-w-md px-5 py-3 rounded-2xl',
message.role === 'user'
? 'bg-[#2288ee] text-white rounded-br-none'
: 'bg-gray-100 text-gray-900 rounded-bl-none',
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-bl-none',
)}
>
{renderMessageContent(message)}
@@ -290,7 +290,7 @@ export default function DebugDialog({
'text-xs mt-2',
message.role === 'user'
? 'text-white/70'
: 'text-gray-500',
: 'text-gray-500 dark:text-gray-400',
)}
>
{message.role === 'user'
@@ -305,7 +305,7 @@ export default function DebugDialog({
</div>
</ScrollArea>
<div className="p-4 pb-0 bg-white flex gap-2">
<div className="p-4 pb-0 bg-white dark:bg-black flex gap-2">
<div className="flex-1 flex items-center gap-2">
{hasAt && (
<AtBadge targetName="webchatbot" onRemove={handleAtRemove} />
@@ -322,23 +322,25 @@ export default function DebugDialog({
? t('pipelines.debugDialog.privateChat')
: t('pipelines.debugDialog.groupChat'),
})}
className="flex-1 rounded-md px-3 py-2 border border-gray-300 focus:border-[#2288ee] transition-none text-base"
className="flex-1 rounded-md px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 focus:border-[#2288ee] transition-none text-base"
/>
{showAtPopover && (
<div
ref={popoverRef}
className="absolute bottom-full left-0 mb-2 w-auto rounded-md border bg-white shadow-lg"
className="absolute bottom-full left-0 mb-2 w-auto rounded-md border bg-white dark:bg-gray-800 dark:border-gray-600 shadow-lg"
>
<div
className={cn(
'flex items-center gap-2 px-4 py-1.5 rounded cursor-pointer',
isHovering ? 'bg-gray-100' : 'bg-white',
isHovering
? 'bg-gray-100 dark:bg-gray-700'
: 'bg-white dark:bg-gray-800',
)}
onClick={handleAtSelect}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
>
<span>
<span className="text-gray-800 dark:text-gray-200">
@webchatbot - {t('pipelines.debugDialog.atTips')}
</span>
</div>
@@ -369,7 +371,7 @@ export default function DebugDialog({
// 原有的Dialog包装
return (
<DialogContent className="!max-w-[70vw] max-w-6xl h-[70vh] p-6 flex flex-col rounded-2xl shadow-2xl bg-white">
<DialogContent className="!max-w-[70vw] max-w-6xl h-[70vh] p-6 flex flex-col rounded-2xl shadow-2xl bg-white dark:bg-black">
{renderContent()}
</DialogContent>
);

View File

@@ -10,12 +10,22 @@
flex-direction: row;
justify-content: space-between;
gap: 0.5rem;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0px 2px 8px 0 rgba(0, 0, 0, 0.1);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.basicInfoContainer {
width: 100%;
height: 100%;
@@ -35,6 +45,11 @@
.basicInfoNameText {
font-size: 1.4rem;
font-weight: 500;
color: #1a1a1a;
}
:global(.dark) .basicInfoNameText {
color: #f0f0f0;
}
.basicInfoDescriptionText {
@@ -48,6 +63,10 @@
color: #b1b1b1;
}
:global(.dark) .basicInfoDescriptionText {
color: #888888;
}
.basicInfoLastUpdatedTimeContainer {
display: flex;
flex-direction: row;
@@ -58,11 +77,21 @@
.basicInfoUpdateTimeIcon {
width: 1.2rem;
height: 1.2rem;
color: #626262;
}
:global(.dark) .basicInfoUpdateTimeIcon {
color: #a0a0a0;
}
.basicInfoUpdateTimeText {
font-size: 1rem;
font-weight: 400;
color: #626262;
}
:global(.dark) .basicInfoUpdateTimeText {
color: #a0a0a0;
}
.operationContainer {
@@ -86,12 +115,20 @@
color: #ffcd27;
}
:global(.dark) .operationDefaultBadgeIcon {
color: #fbbf24;
}
.operationDefaultBadgeText {
font-size: 1rem;
font-weight: 400;
color: #ffcd27;
}
:global(.dark) .operationDefaultBadgeText {
color: #fbbf24;
}
.bigText {
white-space: nowrap;
overflow: hidden;

View File

@@ -342,7 +342,7 @@ export default function PipelineFormComponent({
return (
<>
<div className="!max-w-[70vw] max-w-6xl h-full p-0 flex flex-col bg-white">
<div className="!max-w-[70vw] max-w-6xl h-full p-0 flex flex-col bg-white dark:bg-black">
<Form {...form}>
<form
id="pipeline-form"
@@ -456,7 +456,7 @@ export default function PipelineFormComponent({
</form>
{/* 按钮栏移到 Tabs 外部,始终固定底部 */}
{showButtons && (
<div className="flex justify-end gap-2 pt-4 border-t mb-0 bg-white sticky bottom-0 z-10">
<div className="flex justify-end gap-2 pt-4 border-t mb-0 bg-white dark:bg-black sticky bottom-0 z-10">
{isEditMode && !isDefaultPipeline && (
<Button
type="button"

View File

@@ -125,17 +125,26 @@ export default function PluginConfigPage() {
value={`${sortByValue},${sortOrderValue}`}
onValueChange={handleSortChange}
>
<SelectTrigger className="w-[180px] cursor-pointer bg-white dark:bg-gray-800">
<SelectTrigger className="w-[180px] cursor-pointer bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('pipelines.sortBy')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="created_at,DESC">
<SelectContent className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectItem
value="created_at,DESC"
className="text-gray-900 dark:text-gray-100"
>
{t('pipelines.newestCreated')}
</SelectItem>
<SelectItem value="updated_at,DESC">
<SelectItem
value="updated_at,DESC"
className="text-gray-900 dark:text-gray-100"
>
{t('pipelines.recentlyEdited')}
</SelectItem>
<SelectItem value="updated_at,ASC">
<SelectItem
value="updated_at,ASC"
className="text-gray-900 dark:text-gray-100"
>
{t('pipelines.earliestEdited')}
</SelectItem>
</SelectContent>

View File

@@ -85,7 +85,7 @@ export default function PluginConfigPage() {
<div className={styles.pageContainer}>
<Tabs defaultValue="installed" className="w-full">
<div className="flex flex-row justify-between items-center px-[0.8rem]">
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
<TabsList className="shadow-md py-5 bg-[#f0f0f0] dark:bg-[#2a2a2e]">
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">
{t('plugins.installed')}
</TabsTrigger>
@@ -134,7 +134,7 @@ export default function PluginConfigPage() {
</Tabs>
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="w-[500px] p-6">
<DialogContent className="w-[500px] p-6 bg-white dark:bg-[#1a1a1e]">
<DialogHeader>
<DialogTitle className="flex items-center gap-4">
<GithubIcon className="size-6" />

View File

@@ -34,7 +34,7 @@ export default function PluginCardComponent({
}
return (
<div
className="w-[100%] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem] cursor-pointer"
className="w-[100%] h-[10rem] bg-white dark:bg-[#1f1f22] rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] dark:shadow-[0] p-[1.2rem] cursor-pointer transition-all duration-200 hover:shadow-[0px_2px_8px_0_rgba(0,0,0,0.1)] dark:hover:shadow-[0]"
onClick={onCardClick}
>
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
@@ -50,18 +50,20 @@ export default function PluginCardComponent({
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
<div className="flex flex-col items-start justify-start">
<div className="flex flex-col items-start justify-start">
<div className="text-[0.7rem] text-[#666]">
<div className="text-[0.7rem] text-[#666] dark:text-[#a0a0a0]">
{cardVO.author} /{' '}
</div>
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
<div className="text-[1.2rem] text-black dark:text-[#f0f0f0]">
{cardVO.name}
</div>
<Badge variant="outline" className="text-[0.7rem]">
v{cardVO.version}
</Badge>
</div>
</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">
<div className="text-[0.8rem] text-[#666] dark:text-[#888888] line-clamp-2">
{cardVO.description}
</div>
</div>
@@ -69,14 +71,14 @@ export default function PluginCardComponent({
<div className="w-full flex flex-row items-start justify-start gap-[0.6rem]">
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg
className="w-[1.2rem] h-[1.2rem] text-black"
className="w-[1.2rem] h-[1.2rem] text-black dark:text-[#f0f0f0]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M24 12L18.3431 17.6569L16.9289 16.2426L21.1716 12L16.9289 7.75736L18.3431 6.34315L24 12ZM2.82843 12L7.07107 16.2426L5.65685 17.6569L0 12L5.65685 6.34315L7.07107 7.75736L2.82843 12ZM9.78845 21H7.66009L14.2116 3H16.3399L9.78845 21Z"></path>
</svg>
<div className="text-base text-black font-medium">
<div className="text-base text-black dark:text-[#f0f0f0] font-medium">
{t('plugins.eventCount', {
count: Object.keys(cardVO.event_handlers).length,
})}
@@ -85,14 +87,14 @@ export default function PluginCardComponent({
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg
className="w-[1.2rem] h-[1.2rem] text-black"
className="w-[1.2rem] h-[1.2rem] text-black dark:text-[#f0f0f0]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M5.32943 3.27158C6.56252 2.8332 7.9923 3.10749 8.97927 4.09446C10.1002 5.21537 10.3019 6.90741 9.5843 8.23385L20.293 18.9437L18.8788 20.3579L8.16982 9.64875C6.84325 10.3669 5.15069 10.1654 4.02952 9.04421C3.04227 8.05696 2.7681 6.62665 3.20701 5.39332L5.44373 7.63C6.02952 8.21578 6.97927 8.21578 7.56505 7.63C8.15084 7.04421 8.15084 6.09446 7.56505 5.50868L5.32943 3.27158ZM15.6968 5.15512L18.8788 3.38736L20.293 4.80157L18.5252 7.98355L16.7574 8.3371L14.6361 10.4584L13.2219 9.04421L15.3432 6.92289L15.6968 5.15512ZM8.97927 13.2868L10.3935 14.7011L5.09018 20.0044C4.69966 20.3949 4.06649 20.3949 3.67597 20.0044C3.31334 19.6417 3.28744 19.0699 3.59826 18.6774L3.67597 18.5902L8.97927 13.2868Z"></path>
</svg>
<div className="text-base text-black font-medium">
<div className="text-base text-black dark:text-[#f0f0f0] font-medium">
{t('plugins.toolCount', { count: cardVO.tools.length })}
</div>
</div>
@@ -115,7 +117,9 @@ export default function PluginCardComponent({
<div className="flex items-center justify-center gap-[0.4rem]">
<svg
className={`w-[1.4rem] h-[1.4rem] cursor-pointer ${
cardVO.repository ? 'text-black' : 'text-gray-400'
cardVO.repository
? 'text-black dark:text-[#f0f0f0]'
: 'text-gray-400 dark:text-gray-600'
}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"

View File

@@ -141,15 +141,26 @@ export default function PluginMarketComponent({
value={`${sortByValue},${sortOrderValue}`}
onValueChange={handleSortChange}
>
<SelectTrigger className="w-[180px] ml-2 cursor-pointer">
<SelectTrigger className="w-[180px] ml-2 cursor-pointer bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectValue placeholder={t('plugins.sortBy')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="stars,DESC">{t('plugins.mostStars')}</SelectItem>
<SelectItem value="created_at,DESC">
<SelectContent className="bg-[#ffffff] dark:bg-[#2a2a2e]">
<SelectItem
value="stars,DESC"
className="text-gray-900 dark:text-gray-100"
>
{t('plugins.mostStars')}
</SelectItem>
<SelectItem
value="created_at,DESC"
className="text-gray-900 dark:text-gray-100"
>
{t('plugins.recentlyAdded')}
</SelectItem>
<SelectItem value="pushed_at,DESC">
<SelectItem
value="pushed_at,DESC"
className="text-gray-900 dark:text-gray-100"
>
{t('plugins.recentlyUpdated')}
</SelectItem>
</SelectContent>
@@ -198,7 +209,7 @@ export default function PluginMarketComponent({
isActive={pageNum === nowPage}
onClick={() => handlePageChange(pageNum)}
>
<span className="text-black select-none">
<span className="text-black dark:text-white select-none">
{pageNum}
</span>
</PaginationLink>

View File

@@ -16,7 +16,7 @@ export default function PluginMarketCardComponent({
}
return (
<div className="w-[100%] h-[10rem] bg-white rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] p-[1.2rem]">
<div className="w-[100%] h-[10rem] bg-white dark:bg-[#1f1f22] rounded-[10px] shadow-[0px_2px_2px_0_rgba(0,0,0,0.2)] dark:shadow-[0] p-[1.2rem] transition-all duration-200 hover:shadow-[0px_2px_8px_0_rgba(0,0,0,0.1)] dark:hover:shadow-[0]">
<div className="w-full h-full flex flex-row items-start justify-start gap-[1.2rem]">
<svg
className="w-16 h-16 text-[#2288ee]"
@@ -30,15 +30,17 @@ export default function PluginMarketCardComponent({
<div className="w-full h-full flex flex-col items-start justify-between gap-[0.6rem]">
<div className="flex flex-col items-start justify-start">
<div className="flex flex-col items-start justify-start">
<div className="text-[0.7rem] text-[#666]">
<div className="text-[0.7rem] text-[#666] dark:text-[#a0a0a0]">
{cardVO.author} /{' '}
</div>
<div className="flex flex-row items-center justify-start gap-[0.4rem]">
<div className="text-[1.2rem] text-black">{cardVO.name}</div>
<div className="text-[1.2rem] text-black dark:text-[#f0f0f0]">
{cardVO.name}
</div>
</div>
</div>
<div className="text-[0.8rem] text-[#666] line-clamp-2">
<div className="text-[0.8rem] text-[#666] dark:text-[#888888] line-clamp-2">
{cardVO.description}
</div>
</div>
@@ -46,21 +48,21 @@ export default function PluginMarketCardComponent({
<div className="w-full flex flex-row items-start justify-between gap-[0.6rem]">
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg
className="w-[1.2rem] h-[1.2rem] text-[#ffcd27]"
className="w-[1.2rem] h-[1.2rem] text-[#ffcd27] dark:text-[#fbbf24]"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12.0006 18.26L4.94715 22.2082L6.52248 14.2799L0.587891 8.7918L8.61493 7.84006L12.0006 0.5L15.3862 7.84006L23.4132 8.7918L17.4787 14.2799L19.054 22.2082L12.0006 18.26Z"></path>
</svg>
<div className="text-base text-[#ffcd27] font-medium">
<div className="text-base text-[#ffcd27] dark:text-[#fbbf24] font-medium">
{t('plugins.starCount', { count: cardVO.starCount })}
</div>
</div>
<div className="flex h-full flex-row items-center justify-center gap-[0.4rem]">
<svg
className="w-[1.4rem] h-[1.4rem] text-black cursor-pointer"
className="w-[1.4rem] h-[1.4rem] text-black dark:text-[#f0f0f0] cursor-pointer"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"

View File

@@ -7,13 +7,27 @@
align-items: center;
justify-content: space-evenly;
cursor: pointer;
transition: all 0.2s ease;
}
:global(.dark) .cardContainer {
background-color: #1f1f22;
box-shadow: 0;
}
.cardContainer:hover {
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.05);
}
:global(.dark) .cardContainer:hover {
box-shadow: 0;
}
.createCardContainer {
font-size: 90px;
color: #acacac;
}
:global(.dark) .createCardContainer {
color: #666666;
}

View File

@@ -3,6 +3,7 @@ import 'react-photo-view/dist/react-photo-view.css';
import type { Metadata } from 'next';
import { Toaster } from '@/components/ui/sonner';
import I18nProvider from '@/i18n/I18nProvider';
import { ThemeProvider } from '@/components/providers/theme-provider';
export const metadata: Metadata = {
title: 'LangBot',
@@ -15,12 +16,14 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html>
<html lang="zh" suppressHydrationWarning>
<body className={``}>
<I18nProvider>
{children}
<Toaster />
</I18nProvider>
<ThemeProvider>
<I18nProvider>
{children}
<Toaster />
</I18nProvider>
</ThemeProvider>
</body>
</html>
);

View File

@@ -35,6 +35,7 @@ import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import Link from 'next/link';
import { ThemeToggle } from '@/components/ui/theme-toggle';
const formSchema = (t: (key: string) => string) =>
z.object({
@@ -155,10 +156,11 @@ export default function Login() {
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[375px]">
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:dark:bg-neutral-900">
<Card className="w-[375px] shadow-lg dark:shadow-white/10">
<CardHeader>
<div className="flex justify-end mb-6">
<div className="flex justify-between items-center mb-6">
<ThemeToggle />
<Select
value={currentLanguage}
onValueChange={handleLanguageChange}

View File

@@ -34,6 +34,7 @@ import langbotIcon from '@/app/assets/langbot-logo.webp';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import { ThemeToggle } from '@/components/ui/theme-toggle';
const formSchema = (t: (key: string) => string) =>
z.object({
@@ -139,10 +140,11 @@ export default function Register() {
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[375px]">
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Card className="w-[375px] shadow-lg dark:shadow-white/10">
<CardHeader>
<div className="flex justify-end mb-6">
<div className="flex justify-between items-center mb-6">
<ThemeToggle />
<Select
value={currentLanguage}
onValueChange={handleLanguageChange}

View File

@@ -33,6 +33,7 @@ import { Mail, Lock, ArrowLeft } from 'lucide-react';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import Link from 'next/link';
import { ThemeToggle } from '@/components/ui/theme-toggle';
const REGEXP_ONLY_DIGITS_AND_CHARS = /^[0-9a-zA-Z]+$/;
@@ -84,17 +85,18 @@ export default function ResetPassword() {
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[375px]">
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Card className="w-[375px] shadow-lg dark:shadow-white/10">
<CardHeader>
<div className="flex justify-between items-center mb-6">
<Link
href="/login"
className="flex items-center text-sm text-gray-600 hover:text-gray-900 transition-colors"
className="flex items-center text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100 transition-colors"
>
<ArrowLeft className="h-4 w-4 mr-1" />
{t('resetPassword.backToLogin')}
</Link>
<ThemeToggle />
</div>
<CardTitle className="text-2xl text-center">
{t('resetPassword.title')}

View File

@@ -0,0 +1,18 @@
'use client';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes';
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
{...props}
>
{children}
</NextThemesProvider>
);
}

View File

@@ -76,7 +76,7 @@ function PaginationPrevious({
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon className="text-black" />
<ChevronLeftIcon className="text-black dark:text-white" />
<span className="hidden sm:block"></span>
</PaginationLink>
);
@@ -94,7 +94,7 @@ function PaginationNext({
{...props}
>
<span className="hidden sm:block"></span>
<ChevronRightIcon className="text-black" />
<ChevronRightIcon className="text-black dark:text-white" />
</PaginationLink>
);
}

View File

@@ -0,0 +1,23 @@
'use client';
import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="outline"
size="icon"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
className="h-9 w-9"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
);
}