1
+ import * as React from 'react' ;
1
2
import * as Tabs from '@radix-ui/react-tabs' ;
2
3
import { MessageSquare , Command } from 'lucide-react' ;
3
4
import { SettingsTabValues } from 'librechat-data-provider' ;
@@ -11,10 +12,45 @@ import { cn } from '~/utils';
11
12
export default function Settings ( { open, onOpenChange } : TDialogProps ) {
12
13
const isSmallScreen = useMediaQuery ( '(max-width: 767px)' ) ;
13
14
const localize = useLocalize ( ) ;
15
+ const [ activeTab , setActiveTab ] = React . useState ( SettingsTabValues . GENERAL ) ;
16
+
17
+ const handleKeyDown = ( event : React . KeyboardEvent ) => {
18
+ const tabs = [
19
+ SettingsTabValues . GENERAL ,
20
+ SettingsTabValues . CHAT ,
21
+ SettingsTabValues . BETA ,
22
+ SettingsTabValues . COMMANDS ,
23
+ SettingsTabValues . SPEECH ,
24
+ SettingsTabValues . DATA ,
25
+ SettingsTabValues . ACCOUNT ,
26
+ ] ;
27
+ const currentIndex = tabs . indexOf ( activeTab ) ;
28
+
29
+ switch ( event . key ) {
30
+ case 'ArrowDown' :
31
+ case 'ArrowRight' :
32
+ event . preventDefault ( ) ;
33
+ setActiveTab ( tabs [ ( currentIndex + 1 ) % tabs . length ] ) ;
34
+ break ;
35
+ case 'ArrowUp' :
36
+ case 'ArrowLeft' :
37
+ event . preventDefault ( ) ;
38
+ setActiveTab ( tabs [ ( currentIndex - 1 + tabs . length ) % tabs . length ] ) ;
39
+ break ;
40
+ case 'Home' :
41
+ event . preventDefault ( ) ;
42
+ setActiveTab ( tabs [ 0 ] ) ;
43
+ break ;
44
+ case 'End' :
45
+ event . preventDefault ( ) ;
46
+ setActiveTab ( tabs [ tabs . length - 1 ] ) ;
47
+ break ;
48
+ }
49
+ } ;
14
50
15
51
return (
16
52
< Transition appear show = { open } >
17
- < Dialog as = "div" className = "relative z-50 focus:outline-none " onClose = { onOpenChange } >
53
+ < Dialog as = "div" className = "relative z-50" onClose = { onOpenChange } >
18
54
< TransitionChild
19
55
enter = "ease-out duration-200"
20
56
enterFrom = "opacity-0"
@@ -77,127 +113,93 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
77
113
</ DialogTitle >
78
114
< div className = "max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]" >
79
115
< Tabs . Root
80
- defaultValue = { SettingsTabValues . GENERAL }
116
+ value = { activeTab }
117
+ onValueChange = { ( value : string ) => setActiveTab ( value as SettingsTabValues ) }
81
118
className = "flex flex-col gap-10 md:flex-row"
82
119
orientation = "horizontal"
83
120
>
84
121
< Tabs . List
85
122
aria-label = "Settings"
86
- role = "tablist"
87
- aria-orientation = "horizontal"
88
123
className = { cn (
89
124
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-nowrap overflow-auto sm:max-w-none' ,
90
125
isSmallScreen ? 'flex-row rounded-lg bg-surface-secondary' : '' ,
91
126
) }
92
- style = { { outline : 'none' } }
127
+ onKeyDown = { handleKeyDown }
93
128
>
94
- < Tabs . Trigger
95
- tabIndex = { 0 }
96
- className = { cn (
97
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
98
- isSmallScreen
99
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
100
- : 'bg-surface-tertiary-alt' ,
101
- ) }
102
- value = { SettingsTabValues . GENERAL }
103
- style = { { userSelect : 'none' } }
104
- >
105
- < GearIcon />
106
- { localize ( 'com_nav_setting_general' ) }
107
- </ Tabs . Trigger >
108
- < Tabs . Trigger
109
- tabIndex = { 0 }
110
- className = { cn (
111
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
112
- isSmallScreen
113
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
114
- : 'bg-surface-tertiary-alt' ,
115
- ) }
116
- value = { SettingsTabValues . CHAT }
117
- style = { { userSelect : 'none' } }
118
- >
119
- < MessageSquare className = "icon-sm" />
120
- { localize ( 'com_nav_setting_chat' ) }
121
- </ Tabs . Trigger >
122
- < Tabs . Trigger
123
- tabIndex = { 0 }
124
- className = { cn (
125
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
126
- isSmallScreen
127
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
128
- : 'bg-surface-tertiary-alt' ,
129
- ) }
130
- value = { SettingsTabValues . BETA }
131
- style = { { userSelect : 'none' } }
132
- >
133
- < ExperimentIcon />
134
- { localize ( 'com_nav_setting_beta' ) }
135
- </ Tabs . Trigger >
136
- < Tabs . Trigger
137
- tabIndex = { 0 }
138
- className = { cn (
139
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
140
- isSmallScreen
141
- ? 'flex-1 items-center justify-center text-nowrap text-sm text-text-secondary'
142
- : 'bg-surface-tertiary-alt' ,
143
- ) }
144
- value = { SettingsTabValues . COMMANDS }
145
- style = { { userSelect : 'none' } }
146
- >
147
- < Command className = "icon-sm" />
148
- { localize ( 'com_nav_commands' ) }
149
- </ Tabs . Trigger >
150
- < Tabs . Trigger
151
- tabIndex = { 0 }
152
- className = { cn (
153
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
154
- isSmallScreen
155
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
156
- : 'bg-surface-tertiary-alt' ,
157
- ) }
158
- value = { SettingsTabValues . SPEECH }
159
- style = { { userSelect : 'none' } }
160
- >
161
- < SpeechIcon className = "icon-sm" />
162
- { localize ( 'com_nav_setting_speech' ) }
163
- </ Tabs . Trigger >
164
- < Tabs . Trigger
165
- tabIndex = { 0 }
166
- className = { cn (
167
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
168
- isSmallScreen
169
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
170
- : 'bg-surface-tertiary-alt' ,
171
- ) }
172
- value = { SettingsTabValues . DATA }
173
- style = { { userSelect : 'none' } }
174
- >
175
- < DataIcon />
176
- { localize ( 'com_nav_setting_data' ) }
177
- </ Tabs . Trigger >
178
- < Tabs . Trigger
179
- tabIndex = { 0 }
180
- className = { cn (
181
- 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
182
- isSmallScreen
183
- ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
184
- : 'bg-surface-tertiary-alt' ,
185
- ) }
186
- value = { SettingsTabValues . ACCOUNT }
187
- style = { { userSelect : 'none' } }
188
- >
189
- < UserIcon />
190
- { localize ( 'com_nav_setting_account' ) }
191
- </ Tabs . Trigger >
129
+ { [
130
+ {
131
+ value : SettingsTabValues . GENERAL ,
132
+ icon : < GearIcon /> ,
133
+ label : 'com_nav_setting_general' ,
134
+ } ,
135
+ {
136
+ value : SettingsTabValues . CHAT ,
137
+ icon : < MessageSquare className = "icon-sm" /> ,
138
+ label : 'com_nav_setting_chat' ,
139
+ } ,
140
+ {
141
+ value : SettingsTabValues . BETA ,
142
+ icon : < ExperimentIcon /> ,
143
+ label : 'com_nav_setting_beta' ,
144
+ } ,
145
+ {
146
+ value : SettingsTabValues . COMMANDS ,
147
+ icon : < Command className = "icon-sm" /> ,
148
+ label : 'com_nav_commands' ,
149
+ } ,
150
+ {
151
+ value : SettingsTabValues . SPEECH ,
152
+ icon : < SpeechIcon className = "icon-sm" /> ,
153
+ label : 'com_nav_setting_speech' ,
154
+ } ,
155
+ {
156
+ value : SettingsTabValues . DATA ,
157
+ icon : < DataIcon /> ,
158
+ label : 'com_nav_setting_data' ,
159
+ } ,
160
+ {
161
+ value : SettingsTabValues . ACCOUNT ,
162
+ icon : < UserIcon /> ,
163
+ label : 'com_nav_setting_account' ,
164
+ } ,
165
+ ] . map ( ( { value, icon, label } ) => (
166
+ < Tabs . Trigger
167
+ key = { value }
168
+ className = { cn (
169
+ 'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-text-primary transition-all duration-200 ease-in-out radix-state-active:bg-surface-tertiary radix-state-active:text-text-primary dark:radix-state-active:bg-surface-active' ,
170
+ isSmallScreen
171
+ ? 'flex-1 items-center justify-center text-nowrap p-1 px-3 text-sm text-text-secondary'
172
+ : 'bg-surface-tertiary-alt' ,
173
+ ) }
174
+ value = { value }
175
+ >
176
+ { icon }
177
+ { localize ( label ) }
178
+ </ Tabs . Trigger >
179
+ ) ) }
192
180
</ Tabs . List >
193
181
< div className = "max-h-[373px] overflow-auto sm:w-full sm:max-w-none md:pr-0.5 md:pt-0.5" >
194
- < General />
195
- < Chat />
196
- < Beta />
197
- < Commands />
198
- < Speech />
199
- < Data />
200
- < Account />
182
+ < Tabs . Content value = { SettingsTabValues . GENERAL } >
183
+ < General />
184
+ </ Tabs . Content >
185
+ < Tabs . Content value = { SettingsTabValues . CHAT } >
186
+ < Chat />
187
+ </ Tabs . Content >
188
+ < Tabs . Content value = { SettingsTabValues . BETA } >
189
+ < Beta />
190
+ </ Tabs . Content >
191
+ < Tabs . Content value = { SettingsTabValues . COMMANDS } >
192
+ < Commands />
193
+ </ Tabs . Content >
194
+ < Tabs . Content value = { SettingsTabValues . SPEECH } >
195
+ < Speech />
196
+ </ Tabs . Content >
197
+ < Tabs . Content value = { SettingsTabValues . DATA } >
198
+ < Data />
199
+ </ Tabs . Content >
200
+ < Tabs . Content value = { SettingsTabValues . ACCOUNT } >
201
+ < Account />
202
+ </ Tabs . Content >
201
203
</ div >
202
204
</ Tabs . Root >
203
205
</ div >
0 commit comments