feat(ui): selectable bottom-nav slots and sidebar drag reorder

Let each of the four bottom slots pick a menu from the shared catalog, and allow reordering the sidebar the same way.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 12:14:55 +03:30
co-authored by Cursor
parent 238fb7966c
commit 72b0a819af
2 changed files with 177 additions and 11 deletions
+59 -5
View File
@@ -178,6 +178,64 @@ export function mergeNavItemsFromSample(
return kept.map((item, index) => ({ ...item, order: index + 1 }))
}
/** گزینه‌های قابل انتخاب برای هر جایگاه منوی پایین (سایدبار + خرید و فروش) */
export function getKhanegiBottomNavChoices(): KhanegiNavItem[] {
const sample = getKhanegiNavSample()
const byId = new Map<string, KhanegiNavItem>()
for (const item of sample.bottom) byId.set(item.id, cloneJson(item))
for (const item of sample.sidebar) {
if (item.id === 'home') continue // خانه همان FAB وسط است
if (!byId.has(item.id)) byId.set(item.id, cloneJson(item))
}
return Array.from(byId.values())
}
/** دقیقاً ۴ جایگاه؛ انتخاب ذخیره‌شده از کاتالوگ، بقیه از پیش‌فرض پر می‌شود */
export function mergeBottomNavSlots(
stored: KhanegiNavItem[] | undefined,
): KhanegiNavItem[] {
const choices = getKhanegiBottomNavChoices()
const defaults = getKhanegiNavSample().bottom.slice(0, MAX_BOTTOM_NAV_SLOTS)
const byId = new Map(choices.map((item) => [item.id, item]))
const slots: KhanegiNavItem[] = []
const seen = new Set<string>()
const storedList = Array.isArray(stored) ? stored : []
for (const prev of storedList) {
if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break
if (!prev?.id || seen.has(prev.id)) continue
const base = byId.get(prev.id)
if (!base) continue
seen.add(prev.id)
slots.push({
...base,
label: prev.label || base.label,
icon: prev.icon || base.icon,
visible: typeof prev.visible === 'boolean' ? prev.visible : true,
query: prev.query ?? base.query,
})
}
for (const item of defaults) {
if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break
if (seen.has(item.id)) continue
seen.add(item.id)
slots.push({ ...item, visible: true })
}
for (const item of choices) {
if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break
if (seen.has(item.id)) continue
seen.add(item.id)
slots.push({ ...item, visible: true })
}
return slots.slice(0, MAX_BOTTOM_NAV_SLOTS).map((item, index) => ({
...item,
order: index + 1,
}))
}
export function applyKhanegiNavSample(app: { navigation?: any } | null | undefined) {
if (!app || typeof app !== 'object') return
const sample = getKhanegiNavSample()
@@ -191,11 +249,7 @@ export function applyKhanegiNavSample(app: { navigation?: any } | null | undefin
return
}
app.navigation.sidebar = mergeNavItemsFromSample(sample.sidebar, app.navigation.sidebar)
app.navigation.bottom = mergeNavItemsFromSample(
sample.bottom.slice(0, MAX_BOTTOM_NAV_SLOTS),
app.navigation.bottom,
MAX_BOTTOM_NAV_SLOTS,
).slice(0, MAX_BOTTOM_NAV_SLOTS)
app.navigation.bottom = mergeBottomNavSlots(app.navigation.bottom)
if (!app.navigation.sidebarReferral || typeof app.navigation.sidebarReferral !== 'object') {
app.navigation.sidebarReferral = sample.sidebarReferral
}