fix: mobile sidebar (#6760)
* fix: mobile sidebar * fix(web): prevent iOS sidebar taps from being swallowed
This commit is contained in:
@@ -61,12 +61,14 @@ function ChatMenuItem({
|
|||||||
loading,
|
loading,
|
||||||
onOpen,
|
onOpen,
|
||||||
onNavigate,
|
onNavigate,
|
||||||
|
preload,
|
||||||
}: {
|
}: {
|
||||||
preset: ChatPreset
|
preset: ChatPreset
|
||||||
active: boolean
|
active: boolean
|
||||||
loading: boolean
|
loading: boolean
|
||||||
onOpen: (preset: ChatPreset) => void | Promise<void>
|
onOpen: (preset: ChatPreset) => void | Promise<void>
|
||||||
onNavigate: () => void
|
onNavigate: () => void
|
||||||
|
preload?: false
|
||||||
}) {
|
}) {
|
||||||
if (preset.type === 'web') {
|
if (preset.type === 'web') {
|
||||||
return (
|
return (
|
||||||
@@ -77,6 +79,7 @@ function ChatMenuItem({
|
|||||||
<Link
|
<Link
|
||||||
to='/chat/$chatId'
|
to='/chat/$chatId'
|
||||||
params={{ chatId: preset.id }}
|
params={{ chatId: preset.id }}
|
||||||
|
preload={preload}
|
||||||
onClick={onNavigate}
|
onClick={onNavigate}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -277,6 +280,7 @@ export function ChatPresetsItem({ item }: { item: NavChatPresets }) {
|
|||||||
loading={loadingPresetId === preset.id}
|
loading={loadingPresetId === preset.id}
|
||||||
onOpen={handleOpenExternal}
|
onOpen={handleOpenExternal}
|
||||||
onNavigate={() => setOpenMobile(false)}
|
onNavigate={() => setOpenMobile(false)}
|
||||||
|
preload={isMobile ? false : undefined}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ import {
|
|||||||
} from '@/components/ui/sidebar'
|
} from '@/components/ui/sidebar'
|
||||||
|
|
||||||
import { checkIsActive } from '../lib/url-utils'
|
import { checkIsActive } from '../lib/url-utils'
|
||||||
import {
|
import type {
|
||||||
type NavCollapsible,
|
NavCollapsible,
|
||||||
type NavChatPresets,
|
NavChatPresets,
|
||||||
type NavLink,
|
NavLink,
|
||||||
type NavGroup as NavGroupProps,
|
NavGroup as NavGroupProps,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { ChatPresetsItem } from './chat-presets-item'
|
import { ChatPresetsItem } from './chat-presets-item'
|
||||||
|
|
||||||
@@ -121,13 +121,19 @@ function NavBadge({ children }: { children: ReactNode }) {
|
|||||||
* Sidebar menu link item
|
* Sidebar menu link item
|
||||||
*/
|
*/
|
||||||
function SidebarMenuLink({ item, href }: { item: NavLink; href: string }) {
|
function SidebarMenuLink({ item, href }: { item: NavLink; href: string }) {
|
||||||
const { setOpenMobile } = useSidebar()
|
const { isMobile, setOpenMobile } = useSidebar()
|
||||||
return (
|
return (
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
isActive={checkIsActive(href, item)}
|
isActive={checkIsActive(href, item)}
|
||||||
tooltip={item.title}
|
tooltip={item.title}
|
||||||
render={<Link to={item.url} onClick={() => setOpenMobile(false)} />}
|
render={
|
||||||
|
<Link
|
||||||
|
to={item.url}
|
||||||
|
preload={isMobile ? false : undefined}
|
||||||
|
onClick={() => setOpenMobile(false)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{item.icon && <item.icon className='shrink-0' />}
|
{item.icon && <item.icon className='shrink-0' />}
|
||||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||||
@@ -147,7 +153,7 @@ function SidebarMenuCollapsible({
|
|||||||
item: NavCollapsible
|
item: NavCollapsible
|
||||||
href: string
|
href: string
|
||||||
}) {
|
}) {
|
||||||
const { setOpenMobile } = useSidebar()
|
const { isMobile, setOpenMobile } = useSidebar()
|
||||||
// 检查当前路径是否匹配子菜单项
|
// 检查当前路径是否匹配子菜单项
|
||||||
const isSubItemActive = checkIsActive(href, item)
|
const isSubItemActive = checkIsActive(href, item)
|
||||||
// 使用受控状态,初始值基于当前路径是否匹配
|
// 使用受控状态,初始值基于当前路径是否匹配
|
||||||
@@ -184,7 +190,11 @@ function SidebarMenuCollapsible({
|
|||||||
<SidebarMenuSubButton
|
<SidebarMenuSubButton
|
||||||
isActive={checkIsActive(href, subItem)}
|
isActive={checkIsActive(href, subItem)}
|
||||||
render={
|
render={
|
||||||
<Link to={subItem.url} onClick={() => setOpenMobile(false)} />
|
<Link
|
||||||
|
to={subItem.url}
|
||||||
|
preload={isMobile ? false : undefined}
|
||||||
|
onClick={() => setOpenMobile(false)}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{subItem.icon && <subItem.icon className='shrink-0' />}
|
{subItem.icon && <subItem.icon className='shrink-0' />}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type SidebarViewHeaderProps = {
|
|||||||
*/
|
*/
|
||||||
export function SidebarViewHeader(props: SidebarViewHeaderProps) {
|
export function SidebarViewHeader(props: SidebarViewHeaderProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { setOpenMobile } = useSidebar()
|
const { isMobile, setOpenMobile } = useSidebar()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarHeader className='border-sidebar-border border-b px-2 py-2'>
|
<SidebarHeader className='border-sidebar-border border-b px-2 py-2'>
|
||||||
@@ -58,6 +58,7 @@ export function SidebarViewHeader(props: SidebarViewHeaderProps) {
|
|||||||
render={
|
render={
|
||||||
<Link
|
<Link
|
||||||
to={props.view.parent.to}
|
to={props.view.parent.to}
|
||||||
|
preload={isMobile ? false : undefined}
|
||||||
onClick={() => setOpenMobile(false)}
|
onClick={() => setOpenMobile(false)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ function Sidebar({
|
|||||||
data-sidebar='sidebar'
|
data-sidebar='sidebar'
|
||||||
data-slot='sidebar'
|
data-slot='sidebar'
|
||||||
data-mobile='true'
|
data-mobile='true'
|
||||||
className='bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden'
|
className='bg-sidebar text-sidebar-foreground pointer-events-auto z-60 w-(--sidebar-width) p-0 [&>button]:hidden'
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
|
||||||
@@ -530,6 +530,7 @@ function SidebarMenuButton({
|
|||||||
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
||||||
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
||||||
const { isMobile, state } = useSidebar()
|
const { isMobile, state } = useSidebar()
|
||||||
|
const tooltipEnabled = Boolean(tooltip) && !isMobile && state === 'collapsed'
|
||||||
const comp = useRender({
|
const comp = useRender({
|
||||||
defaultTagName: 'button',
|
defaultTagName: 'button',
|
||||||
props: mergeProps<'button'>(
|
props: mergeProps<'button'>(
|
||||||
@@ -538,7 +539,7 @@ function SidebarMenuButton({
|
|||||||
},
|
},
|
||||||
props
|
props
|
||||||
),
|
),
|
||||||
render: !tooltip ? render : <TooltipTrigger render={render} />,
|
render: tooltipEnabled ? <TooltipTrigger render={render} /> : render,
|
||||||
state: {
|
state: {
|
||||||
slot: 'sidebar-menu-button',
|
slot: 'sidebar-menu-button',
|
||||||
sidebar: 'menu-button',
|
sidebar: 'menu-button',
|
||||||
@@ -547,7 +548,7 @@ function SidebarMenuButton({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!tooltip) {
|
if (!tooltipEnabled || !tooltip) {
|
||||||
return comp
|
return comp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,12 +562,7 @@ function SidebarMenuButton({
|
|||||||
<TooltipProvider delay={0}>
|
<TooltipProvider delay={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{comp}
|
{comp}
|
||||||
<TooltipContent
|
<TooltipContent side='right' align='center' {...tooltip} />
|
||||||
side='right'
|
|
||||||
align='center'
|
|
||||||
hidden={state !== 'collapsed' || isMobile}
|
|
||||||
{...tooltip}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user