You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何独立控制Carbon UI Shell的两个Header Panel展开状态?

如何独立控制Carbon UI Shell中多个Header Panel的展开/收起

我参考Carbon Design System的UI-shell-right-panel文档创建了两个Header Panel,分别用于通知和应用切换功能。但实际运行时第二个Panel总是覆盖第一个,无论点击哪个操作图标,都只会展开第二个Panel。请问如何实现点击对应图标展开对应Panel的独立控制?核心代码如下:

import React from "react";
import {
    HeaderContainer, Header, SkipToContent, HeaderMenuButton, HeaderName,
    HeaderNavigation, HeaderMenu, HeaderMenuItem, HeaderGlobalBar,
    HeaderGlobalAction, SideNav, SideNavItems, Content,
    SideNavMenu, SideNavMenuItem, Theme, ExpandableSearch, ToastNotification, HeaderPanel, SwitcherItem, SwitcherDivider, Switcher
} from '@carbon/react';
import {
    Notification,
    Search,
    Switcher as SwitcherIcon,
    Fade,
} from '@carbon/react/icons';

import { Route, Routes, BrowserRouter, Link } from 'react-router-dom';
import ErrorBoundary from "../../components/ErrorBoundary";
import LandingPage from '../LandingPage';
import NotFound from '../../components/NotFound';
import {Contributing} from "../contributing/contributing.tsx";
import {HeaderContainerRenderProps} from "@carbon/react/lib/components/UIShell/HeaderContainer";

interface StateType{
    activeItem: string,
    expandingAction:string
}

export  class UIShell extends React.Component {
    state: StateType;
    constructor(props:any) {
        super(props);
        this.state = {
            activeItem: `/${window.location.pathname.split('/')[1] ?? ''}`,
            expandingAction:''
        };
        this.setExpandingAction = this.setExpandingAction.bind(this);
    }

    async setExpandingAction(action:string){
        await this.setState(()=>{
            return {expandingAction:action};
        })
    }


    render() {
        return (
            <BrowserRouter>
                <Theme theme='g90'>
                    <HeaderContainer  render={({ isSideNavExpanded, onClickSideNavExpand }:HeaderContainerRenderProps) => (
                            <div>
                                <Header aria-label="IBM Executive Performance Platform">
                                    <SkipToContent />
                                    <HeaderMenuButton aria-label="Open menu" onClick={onClickSideNavExpand} isActive={isSideNavExpanded}/>
                                    <HeaderName href="/" prefix="Carbon">Template</HeaderName>
                                    <HeaderNavigation aria-label="Carbon React App">
                                        <HeaderMenuItem href="/contributing">Contributing</HeaderMenuItem>
                                        <HeaderMenuItem>Contact</HeaderMenuItem>
                                        <HeaderMenu aria-label="How To" menuLinkName="How To">
                                            <HeaderMenuItem href="#one">Sub-link 1</HeaderMenuItem>
                                            <HeaderMenuItem href="#two">Sub-link 2</HeaderMenuItem>
                                            <HeaderMenuItem href="#three">Sub-link 3</HeaderMenuItem>
                                        </HeaderMenu>
                                    </HeaderNavigation>
                                    <HeaderGlobalBar>
                                        <div >
                                            <ExpandableSearch  size="lg" labelText="Search" closeButtonLabelText="Clear search input" id="search-expandable-1" onChange={() => {}} onKeyDown={() => {}} />
                                        </div>
                                        <HeaderGlobalAction aria-label="Notifications" aria-expanded={isSideNavExpanded} isActive={isSideNavExpanded}
                                                            onClick={()=>{onClickSideNavExpand();}} tooltipAlignment="end">
                                            <Notification  size={20}  />
                                        </HeaderGlobalAction>
                                        <HeaderGlobalAction aria-label={isSideNavExpanded ? 'Close switcher' : 'Open switcher'} aria-expanded={isSideNavExpanded} isActive={isSideNavExpanded}
                                                            onClick={()=>{onClickSideNavExpand();}} tooltipAlignment="end" >
                                            <SwitcherIcon size={20} />
                                        </HeaderGlobalAction>
                                    </HeaderGlobalBar>
                                    
                                    {/*notification panel */}
                                    <HeaderPanel expanded={isSideNavExpanded} onHeaderPanelFocus={onClickSideNavExpand} href="#notification">
                                        <Switcher aria-label="Switcher Container" expanded={isSideNavExpanded}>
                                            <SwitcherItem aria-label="Link 1" href="#">
                                                Successfully Created a performance report
                                            </SwitcherItem>
                                            <SwitcherDivider />
                                            <SwitcherItem href="#" aria-label="Link 2">
                                                You've got a evaluation from PEM
                                            </SwitcherItem>
                                        </Switcher>
                                    </HeaderPanel>

                                    {/*app switcher panel */}
                                    <HeaderPanel expanded={isSideNavExpanded} onHeaderPanelFocus={onClickSideNavExpand}>
                                        <Switcher aria-label="Switcher Container" expanded={isSideNavExpanded}>
                                            <SwitcherItem aria-label="Link 1" href="#">
                                                Link 1
                                            </SwitcherItem>
                                            <SwitcherDivider />
                                            <SwitcherItem href="#" aria-label="Link 2">
                                                Link 2
                                            </SwitcherItem>
                                            <SwitcherItem href="#" aria-label="Link 3">
                                                Link 3
                                            </SwitcherItem>
                                            <SwitcherItem href="#" aria-label="Link 4">
                                                Link 4
                                            </SwitcherItem>
                                            <SwitcherItem href="#" aria-label="Link 5">
                                                Link 5
                                            </SwitcherItem>
                                            <SwitcherDivider />
                                            <SwitcherItem href="#" aria-label="Link 6">
                                                Link 6
                                            </SwitcherItem>
                                        </Switcher>

                                    </HeaderPanel>
                                    <ErrorBoundary>
                                        <SideNav aria-label="Side navigation" expanded={isSideNavExpanded}>
                                            <SideNavItems>
                                                <SideNavMenuItem element={Link} to='/'
                                                    isActive={this.state.activeItem === '/'} 
                                                    onClick={() => { this.setState({ activeItem: '/' }) }}>
                                                    Overview
                                                </SideNavMenuItem>
                                                <SideNavMenu renderIcon={Fade} title="Inventory" defaultExpanded>
                                                    <SideNavMenuItem element={Link} to='/inventory/items'
                                                        isActive={this.state.activeItem === '/inventory/items'}
                                                        onClick={() => { this.setState({ activeItem: '/inventory/items' }) }}>
                                                        Items
                                                    </SideNavMenuItem>
                                                </SideNavMenu>
                                                <SideNavMenu renderIcon={Fade} title="Management">
                                                    <SideNavMenuItem href="#">
                                                        Link
                                                    </SideNavMenuItem>
                                                    <SideNavMenuItem href="#">
                                                        Link
                                                    </SideNavMenuItem>
                                                    <SideNavMenuItem href="#">
                                                        Link
                                                    </SideNavMenuItem>
                                                </SideNavMenu>
                                                <SideNavMenu
                                                    renderIcon={Fade}
                                                    title="Docs">
                                                    <SideNavMenuItem href="#">
                                                        Link
                                                    </SideNavMenuItem>
                                                    <SideNavMenuItem href="#">
                                                        Link
                                                    </SideNavMenuItem>
                                                </SideNavMenu>
                                            </SideNavItems>
                                        </SideNav>
                                    </ErrorBoundary>
                                </Header>
                            </div>
                        )}
                    />
                </Theme>
                <Content className='content'>
                    <Routes>
                        <Route path="/" element={<LandingPage />} />
                        <Route path="/contributing" element={<Contributing />} />
                        <Route path="*" element={<NotFound />} />
                    </Routes>
                </Content>
            </BrowserRouter>
        );
    }
}

问题根源

你用了同一个isSideNavExpanded状态控制两个Header Panel的展开/收起,且两个图标点击都触发同一个onClickSideNavExpand方法,导致无论点击哪个图标,两个Panel都会同时尝试展开。由于第二个Panel在DOM结构中靠后,会覆盖第一个Panel,最终只显示第二个。

解决方案:独立状态管理

为每个Panel维护独立的状态变量,修改点击逻辑实现精准控制:

import React from "react";
import {
    HeaderContainer, Header, SkipToContent, HeaderMenuButton, HeaderName,
    HeaderNavigation, HeaderMenu, HeaderMenuItem, HeaderGlobalBar,
    HeaderGlobalAction, SideNav, SideNavItems, Content,
    SideNavMenu, SideNavMenuItem, Theme, ExpandableSearch, ToastNotification, HeaderPanel, SwitcherItem, SwitcherDivider, Switcher
} from '@carbon/react';
import {
    Notification,
    Search,
    Switcher as SwitcherIcon,
    Fade,
} from '@carbon/react/icons';

import { Route, Routes, BrowserRouter, Link } from 'react-router-dom';
import ErrorBoundary from "../../components/ErrorBoundary";
import LandingPage from '../LandingPage';
import NotFound from '../../components/NotFound';
import {Contributing} from "../contributing/contributing.tsx";
import {HeaderContainerRenderProps} from "@carbon/react/lib/components/UIShell/HeaderContainer";

interface StateType{
    activeItem: string,
    isNotificationPanelExpanded: boolean,
    isSwitcherPanelExpanded: boolean
}

export class UIShell extends React.Component {
    state: StateType;
    constructor(props:any) {
        super(props);
        this.state = {
            activeItem: `/${window.location.pathname.split('/')[1] ?? ''}`,
            isNotificationPanelExpanded: false,
            isSwitcherPanelExpanded: false
        };
        this.toggleNotificationPanel = this.toggleNotificationPanel.bind(this);
        this.toggleSwitcherPanel = this.toggleSwitcherPanel.bind(this);
        this.closeNotificationPanel = this.closeNotificationPanel.bind(this);
        this.closeSwitcherPanel = this.closeSwitcherPanel.bind(this);
    }

    // 切换通知面板:展开时自动关闭切换器面板
    toggleNotificationPanel() {
        this.setState(prevState => ({
            isNotificationPanelExpanded: !prevState.isNotificationPanelExpanded,
            isSwitcherPanelExpanded: false
        }));
    }

    // 切换切换器面板:展开时自动关闭通知面板
    toggleSwitcherPanel() {
        this.setState(prevState => ({
            isSwitcherPanelExpanded: !prevState.isSwitcherPanelExpanded,
            isNotificationPanelExpanded: false
        }));
    }

    // 关闭通知面板
    closeNotificationPanel() {
        this.setState({ isNotificationPanelExpanded: false });
    }

    // 关闭切换器面板
    closeSwitcherPanel() {
        this.setState({ isSwitcherPanelExpanded: false });
    }

    render() {
        return (
            <BrowserRouter>
                <Theme theme='g90'>
                    <HeaderContainer render={({ isSideNavExpanded, onClickSideNavExpand }:HeaderContainerRenderProps) => (
                        <div>
                            <Header aria-label="IBM Executive Performance Platform">
                                <SkipToContent />
                                <HeaderMenuButton aria-label="Open menu" onClick={onClickSideNavExpand} isActive={isSideNavExpanded}/>
                                <HeaderName href="/" prefix="Carbon">Template</HeaderName>
                                <HeaderNavigation aria-label="Carbon React App">
                                    <HeaderMenuItem href="/contributing">Contributing</HeaderMenuItem>
                                    <HeaderMenuItem>Contact</HeaderMenuItem>
                                    <HeaderMenu aria-label="How To" menuLinkName="How To">
                                        <HeaderMenuItem href="#one">Sub-link 1</HeaderMenuItem>
                                        <HeaderMenuItem href="#two">Sub-link 2</HeaderMenuItem>
                                        <HeaderMenuItem href="#three">Sub-link 3</HeaderMenuItem>
                                    </HeaderMenu>
                                </HeaderNavigation>
                                <HeaderGlobalBar>
                                    <div >
                                        <ExpandableSearch  size="lg" labelText="Search" closeButtonLabelText="Clear search input" id="search-expandable-1" onChange={() => {}} onKeyDown={() => {}} />
                                    </div>
                                    <HeaderGlobalAction 
                                        aria-label="Notifications" 
                                        aria-expanded={this.state.isNotificationPanelExpanded} 
                                        isActive={this.state.isNotificationPanelExpanded}
                                        onClick={this.toggleNotificationPanel} 
                                        tooltipAlignment="end"
                                    >
                                        <Notification  size={20}  />
                                    </HeaderGlobalAction>
                                    <HeaderGlobalAction 
                                        aria-label={this.state.isSwitcherPanelExpanded ? 'Close switcher' : 'Open switcher'} 
                                        aria-expanded={this.state.isSwitcherPanelExpanded} 
                                        isActive={this.state.isSwitcherPanelExpanded}
                                        onClick={this.toggleSwitcherPanel} 
                                        tooltipAlignment="end" 
                                    >
                                        <SwitcherIcon size={20} />
                                    </HeaderGlobalAction>
                                </HeaderGlobalBar>
                                
                                {/* 通知面板:绑定独立状态和关闭事件 */}
                                <HeaderPanel 
                                    expanded={this.state.isNotificationPanelExpanded} 
                                    onHeaderPanelFocus={this.closeNotificationPanel}
                                    onBlur={this.closeNotificationPanel}
                                    href="#notification"
                                >
                                    <Switcher aria-label="Notifications Container" expanded={this.state.isNotificationPanelExpanded}>
                                        <SwitcherItem aria-label="Link 1" href="#">
                                            Successfully Created a performance report
                                        </SwitcherItem>
                                        <SwitcherDivider />
                                        <SwitcherItem href="#" aria-label="Link 2">
                                            You've got a evaluation from PEM
                                        </SwitcherItem>
                                    </Switcher>
                                </HeaderPanel>

                                {/* 应用切换面板:绑定独立状态和关闭事件 */}
                                <HeaderPanel 
                                    expanded={this.state.isSwitcherPanelExpanded} 
                                    onHeaderPanelFocus={this.closeSwitcherPanel}
                                    onBlur={this.closeSwitcherPanel}
                                >
                                    <Switcher aria-label="Switcher Container" expanded={this.state.isSwitcherPanelExpanded}>
                                        <SwitcherItem aria-label="Link 1" href="#">
                                            Link 1
                                        </SwitcherItem>
                                        <SwitcherDivider />
                                        <SwitcherItem href="#" aria-label="Link 2">
                                            Link 2
                                        </SwitcherItem>
                                        <SwitcherItem href="#" aria-label="Link 3">
                                            Link 3
                                        </SwitcherItem>
                                        <SwitcherItem href="#" aria-label="Link 4">
                                            Link 4
                                        </SwitcherItem>
                                        <SwitcherItem href="#" aria-label="Link 5">
                                            Link 5
                                        </SwitcherItem>
                                        <SwitcherDivider />
                                        <SwitcherItem href="#" aria-label="Link 6">
                                            Link 6
                                        </SwitcherItem>
                                    </Switcher>
                                </HeaderPanel>
                                <ErrorBoundary>
                                    <SideNav aria-label="Side navigation" expanded={isSideNavExpanded}>
                                        <SideNavItems>
                                            <SideNavMenuItem element={Link} to='/'
                                                isActive={this.state.activeItem === '/'} 
                                                onClick={() => { this.setState({ activeItem: '/' }) }}>
                                                Overview
                                            </SideNavMenuItem>
                                            <SideNavMenu renderIcon={Fade} title="Inventory" defaultExpanded>
                                                <SideNavMenuItem element={Link} to='/inventory/items'
                                                    isActive={this.state.activeItem === '/inventory/items'}
                                                    onClick={() => { this.setState({ activeItem: '/inventory/items' }) }}>
                                                    Items
                                                </SideNavMenuItem>
                                            </SideNavMenu>
                                            <SideNavMenu renderIcon={Fade} title="Management">
                                                <SideNavMenuItem href="#">
                                                    Link
                                                </SideNavMenuItem>
                                                <SideNavMenuItem href="#">
                                                    Link
                                                </SideNavMenuItem>
                                                <SideNavMenuItem href="#">
                                                    Link
                                                </SideNavMenuItem>
                                            </SideNavMenu>
                                            <SideNavMenu
                                                renderIcon={Fade}
                                                title="Docs">
                                                <SideNavMenuItem href="#">
                                                    Link
                                                </SideNavMenuItem>
                                                <SideNavMenuItem href="#">
                                                    Link
                                                </SideNavMenuItem>
                                            </SideNavMenu>
                                        </SideNavItems>
                                    </SideNav>
                                </ErrorBoundary>
                            </Header>
                        </div>
                    )}
                    />
                </Theme>
                <Content className='content'>
                    <Routes>
                        <Route path="/" element={<LandingPage />} />
                        <Route path="/contributing" element={<Contributing />} />
                        <Route path="*" element={<NotFound />} />
                    </Routes>
                </Content>
            </BrowserRouter>
        );
    }
}

核心修改点

  • 状态分离:新增isNotificationPanelExpanded和isSwitcherPanelExpanded两个独立状态,分别控制对应Panel的显示/隐藏
  • 互斥展开逻辑:点击一个图标时,自动关闭另一个Panel,避免重叠
  • 独立绑定状态:每个HeaderPanel的expanded属性绑定各自的状态变量,不再复用侧边栏状态
  • 完善关闭交互:添加onBlur和onHeaderPanelFocus事件,实现点击外部或焦点离开时自动关闭面板
  • 无障碍优化:图标按钮的aria-expanded和isActive属性同步对应Panel的状态,符合无障碍规范

内容的提问来源于stack exchange,提问作者user1231111

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:39:50