Skip to content

Commit 99fadeb

Browse files
committed
Implement nested navigation items in settings and update the active state tracking to use scroll-based detection instead of stuck sections
- add missing translations to 'configurations.subtitle' - Subsections should be in the sidebar as less pronounced buttons. Use indent guides for clearer UX. - because level 2 is the further nested it can be, simplify naming to just 'nested' - NavItem type is too much simplified, we don't like that nested items use prefix of their parents - rename 'default configurations' to 'defaults' - rename 'edit format instructions' to 'edit formats' - last nested item should have 5px margin below - use [Fragment] - when scrolling to nested navigation item, it scrolls 5px too far
1 parent dd5cc62 commit 99fadeb

10 files changed

Lines changed: 484 additions & 399 deletions

File tree

‎apps/editor/src/views/settings/frontend/Home/Home.tsx‎

Lines changed: 89 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,38 @@ import {
2727
} from '@/constants/instructions'
2828
import { default_system_instructions } from '@shared/constants/default-system-instructions'
2929

30-
type NavItem =
30+
export type NavItem =
3131
| 'general'
32+
| 'checkpoints'
33+
| 'edit-format'
34+
| 'chatbots'
3235
| 'model-providers'
3336
| 'configurations'
37+
| 'defaults'
38+
| 'instructions'
3439

35-
type NavConfigItem = { id: NavItem; label: TranslationKey }
40+
type NavConfigItem = { id: NavItem; label: TranslationKey; is_nested?: boolean }
3641

3742
const NAV_ITEMS_CONFIG: NavConfigItem[] = [
3843
{
3944
id: 'general',
4045
label: 'sidebar.general'
4146
},
47+
{
48+
id: 'checkpoints',
49+
label: 'general.checkpoints.title',
50+
is_nested: true
51+
},
52+
{
53+
id: 'edit-format',
54+
label: 'general.edit-formats.title',
55+
is_nested: true
56+
},
57+
{
58+
id: 'chatbots',
59+
label: 'general.chatbots.title',
60+
is_nested: true
61+
},
4262
{
4363
id: 'model-providers',
4464
label: 'sidebar.model-providers'
@@ -47,6 +67,16 @@ const NAV_ITEMS_CONFIG: NavConfigItem[] = [
4767
id: 'configurations',
4868
label: 'sidebar.configurations'
4969
},
70+
{
71+
id: 'defaults',
72+
label: 'configurations.defaults.title',
73+
is_nested: true
74+
},
75+
{
76+
id: 'instructions',
77+
label: 'configurations.instructions.title',
78+
is_nested: true
79+
}
5080
]
5181

5282
type Props = {
@@ -129,43 +159,23 @@ export const Home: React.FC<Props> = (props) => {
129159
const scroll_container_ref = useRef<HTMLDivElement>(null)
130160
const section_refs = useRef<Record<NavItem, HTMLDivElement | null>>({
131161
general: null,
162+
checkpoints: null,
163+
'edit-format': null,
164+
chatbots: null,
132165
'model-providers': null,
133-
configurations: null
166+
configurations: null,
167+
defaults: null,
168+
instructions: null
134169
})
170+
171+
const set_section_ref = useCallback((id: NavItem, el: HTMLDivElement | null) => {
172+
section_refs.current[id] = el
173+
}, [])
174+
135175
const [commit_instructions, set_commit_instructions] = useState('')
136176
const [voice_input_instructions, set_voice_input_instructions] = useState('')
137177
const [edit_context_instructions, set_edit_context_instructions] =
138178
useState('')
139-
const [stuck_sections, set_stuck_sections] = useState(new Set<NavItem>())
140-
141-
const handle_stuck_change = useCallback((id: NavItem, is_stuck: boolean) => {
142-
set_stuck_sections((prev) => {
143-
const is_currently_stuck = prev.has(id)
144-
if (is_stuck === is_currently_stuck) {
145-
return prev
146-
}
147-
const next = new Set(prev)
148-
if (is_stuck) {
149-
next.add(id)
150-
} else {
151-
next.delete(id)
152-
}
153-
return next
154-
})
155-
}, [])
156-
157-
const general_on_stuck_change = useCallback(
158-
(is_stuck: boolean) => handle_stuck_change('general', is_stuck),
159-
[handle_stuck_change]
160-
)
161-
const model_providers_on_stuck_change = useCallback(
162-
(is_stuck: boolean) => handle_stuck_change('model-providers', is_stuck),
163-
[handle_stuck_change]
164-
)
165-
const configurations_on_stuck_change = useCallback(
166-
(is_stuck: boolean) => handle_stuck_change('configurations', is_stuck),
167-
[handle_stuck_change]
168-
)
169179

170180
const get_has_warning = (id: NavItem): boolean => {
171181
if (id == 'model-providers') {
@@ -177,9 +187,37 @@ export const Home: React.FC<Props> = (props) => {
177187
}
178188
}
179189

180-
const nav_item_ids = NAV_ITEMS_CONFIG.map((item) => item.id)
181-
const active_nav_item_id =
182-
nav_item_ids.filter((id) => stuck_sections.has(id)).pop() || nav_item_ids[0]
190+
const [active_nav_item_id, set_active_nav_item_id] = useState<NavItem>(NAV_ITEMS_CONFIG[0].id)
191+
192+
useEffect(() => {
193+
const scroll_container = scroll_container_ref.current
194+
if (!scroll_container) return
195+
196+
const handle_scroll = () => {
197+
const container_rect = scroll_container.getBoundingClientRect()
198+
let new_active_id = NAV_ITEMS_CONFIG[0].id
199+
200+
for (const item of NAV_ITEMS_CONFIG) {
201+
const el = section_refs.current[item.id]
202+
if (el) {
203+
const rect = el.getBoundingClientRect()
204+
if (rect.top <= container_rect.top + 150) {
205+
new_active_id = item.id
206+
}
207+
}
208+
}
209+
set_active_nav_item_id(new_active_id)
210+
}
211+
212+
scroll_container.addEventListener('scroll', handle_scroll)
213+
window.addEventListener('resize', handle_scroll)
214+
setTimeout(handle_scroll, 50)
215+
216+
return () => {
217+
scroll_container.removeEventListener('scroll', handle_scroll)
218+
window.removeEventListener('resize', handle_scroll)
219+
}
220+
}, [])
183221

184222
useEffect(() => {
185223
set_commit_instructions(props.commit_message_instructions || '')
@@ -202,7 +240,14 @@ export const Home: React.FC<Props> = (props) => {
202240
const section_rect = section.getBoundingClientRect()
203241

204242
const offset = section_rect.top - container_rect.top
205-
const target_scroll_top = scroll_container.scrollTop + offset
243+
244+
let extra_offset = 0
245+
const is_subsection = NAV_ITEMS_CONFIG.find((i) => i.id === item_id)?.is_nested
246+
if (is_subsection) {
247+
extra_offset = -112
248+
}
249+
250+
const target_scroll_top = scroll_container.scrollTop + offset + extra_offset
206251

207252
scroll_container.scrollTo({
208253
top: target_scroll_top,
@@ -240,12 +285,15 @@ export const Home: React.FC<Props> = (props) => {
240285
is_active={item.id === active_nav_item_id}
241286
has_warning={get_has_warning(item.id)}
242287
on_click={(e) => handle_nav_click(e, item.id)}
288+
is_nested={item.is_nested}
289+
is_last_nested={item.is_nested && !NAV_ITEMS_CONFIG[i + 1]?.is_nested}
243290
/>
244291
)
245292
})}
246293
>
247294
<GeneralSection
248-
ref={(el) => (section_refs.current['general'] = el)}
295+
ref={(el) => set_section_ref('general', el)}
296+
set_section_ref={set_section_ref}
249297
context_size_warning_threshold={props.context_size_warning_threshold}
250298
on_context_size_warning_threshold_change={
251299
props.on_context_size_warning_threshold_change
@@ -288,7 +336,6 @@ export const Home: React.FC<Props> = (props) => {
288336
ai_studio_user_id={props.ai_studio_user_id}
289337
on_gemini_user_id_change={props.on_gemini_user_id_change}
290338
on_ai_studio_user_id_change={props.on_ai_studio_user_id_change}
291-
on_stuck_change={general_on_stuck_change}
292339
auto_run_intelligent_update={props.auto_run_intelligent_update}
293340
on_auto_run_intelligent_update_change={props.on_auto_run_intelligent_update_change}
294341
include_prompts_in_commit_messages={props.include_prompts_in_commit_messages}
@@ -298,10 +345,9 @@ export const Home: React.FC<Props> = (props) => {
298345
/>
299346

300347
<UiSection
301-
ref={(el) => (section_refs.current['model-providers'] = el)}
348+
ref={(el) => set_section_ref('model-providers', el)}
302349
title={t('sidebar.model-providers')}
303350
subtitle={t('model-providers.subtitle')}
304-
on_stuck_change={model_providers_on_stuck_change}
305351
actions={
306352
<UiButton on_click={() => props.on_add_provider()}>
307353
{t('action.add-new')}
@@ -381,7 +427,8 @@ export const Home: React.FC<Props> = (props) => {
381427
</UiSection>
382428

383429
<ConfigurationsSection
384-
ref={(el) => (section_refs.current['configurations'] = el)}
430+
ref={(el) => set_section_ref('configurations', el)}
431+
set_section_ref={set_section_ref}
385432
api_configurations={props.api_configurations}
386433
defaults={props.defaults}
387434
set_api_configurations={props.set_api_configurations}
@@ -392,7 +439,6 @@ export const Home: React.FC<Props> = (props) => {
392439
on_delete_api_configuration={props.on_delete_api_configuration}
393440
on_set_default_api_configuration={props.on_set_default_api_configuration}
394441
on_select_default_api_configuration={props.on_select_default_api_configuration}
395-
on_stuck_change={configurations_on_stuck_change}
396442
edit_context_instructions={edit_context_instructions}
397443
commit_instructions={commit_instructions}
398444
voice_input_instructions={voice_input_instructions}

0 commit comments

Comments
 (0)