From 48a0626150652bfca1717faa7dfb25865c5b1432 Mon Sep 17 00:00:00 2001 From: Yahiewi Date: Tue, 30 Jun 2026 14:44:33 +0200 Subject: [PATCH 1/2] Add custom settings icon API --- src/document/factory.tsx | 3 +++ src/token.ts | 3 +++ src/topbar/index.tsx | 2 ++ src/topbar/menuComponent.tsx | 31 ++++++++++++++++++++++++++++--- src/topbar/settingDialog.tsx | 2 +- src/topbar/topbarWidget.tsx | 16 ++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/document/factory.tsx b/src/document/factory.tsx index 1dea786..2357578 100644 --- a/src/document/factory.tsx +++ b/src/document/factory.tsx @@ -66,6 +66,7 @@ export class NotebookGridWidgetFactory extends ABCWidgetFactory< } else { topbarWidget = this._spectaTopbar; } + const topbar = topbarWidget as TopbarWidget | undefined; const menu = ( ); diff --git a/src/token.ts b/src/token.ts index 2225a39..9400a79 100644 --- a/src/token.ts +++ b/src/token.ts @@ -74,6 +74,7 @@ export type ISpectaUrlFactory = (path: string, ui?: string) => string; export interface ISpectaUiSwitcher { uis: IUiOption[]; switchTo: (path: string, ui: string) => void; + label?: string; } export const ISpectaLayoutRegistry = new Token( 'specta:ISpectaLayoutRegistry' @@ -108,6 +109,8 @@ export interface ISpectaTopbarWidget { ) => Widget; addSettingsWidget?: (widget: ISpectaWidget) => void; settingsWidgets?: ISpectaWidget[]; + setSettingsIcon?: (icon: JSX.Element) => void; + customIcon?: JSX.Element; } export const ISpectaTopbarWidgetToken = new Token( 'specta:ISpectaTopbarWidget' diff --git a/src/topbar/index.tsx b/src/topbar/index.tsx index 5aec8b6..69382a4 100644 --- a/src/topbar/index.tsx +++ b/src/topbar/index.tsx @@ -74,6 +74,8 @@ export const topbarPlugin: JupyterFrontEndPlugin< uiSwitcher={uiSwitcher} currentPath={path} currentUi={isSpecta ? 'specta' : 'lab'} + settingsIconChanged={widget.settingsIconChanged} + customIcon={widget.customIcon} /> ); widget.addReactWidget(menu, 'right', 10000); diff --git a/src/topbar/menuComponent.tsx b/src/topbar/menuComponent.tsx index a7a75c5..f5a9c9b 100644 --- a/src/topbar/menuComponent.tsx +++ b/src/topbar/menuComponent.tsx @@ -1,5 +1,6 @@ import { IThemeManager } from '@jupyterlab/apputils'; import React, { useState, useRef, useEffect } from 'react'; +import { ISignal } from '@lumino/signaling'; import { GearIcon } from '../components/icon/gear'; import { IconButton } from '../components/iconButton'; @@ -13,12 +14,19 @@ interface IProps { uiSwitcher?: ISpectaUiSwitcher | null; currentPath?: string | null; currentUi?: string; + settingsIconChanged?: ISignal; + customIcon?: JSX.Element; } export function MenuComponent(props: IProps): JSX.Element { const [open, setOpen] = useState(false); const buttonRef = useRef(null); const dialogRef = useRef(null); + const [customIcon, setCustomIcon] = useState(props.customIcon); + + useEffect(() => { + setCustomIcon(props.customIcon); + }, [props.customIcon]); useEffect(() => { const handleClickOutside = (e: any) => { @@ -34,14 +42,31 @@ export function MenuComponent(props: IProps): JSX.Element { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); + + useEffect(() => { + const signal = props.settingsIconChanged; + if (!signal) { + return; + } + const handler = (_sender: any, icon: JSX.Element) => { + setCustomIcon(icon); + }; + signal.connect(handler); + return () => { + signal.disconnect(handler); + }; + }, [props.settingsIconChanged]); + + const menuIcon = customIcon || ( + + ); + return (
setOpen(!open)} - icon={ - - } + icon={menuIcon} /> {open && ( diff --git a/src/topbar/settingDialog.tsx b/src/topbar/settingDialog.tsx index 67b51b9..e5b9218 100644 --- a/src/topbar/settingDialog.tsx +++ b/src/topbar/settingDialog.tsx @@ -198,7 +198,7 @@ export const SettingContent = (props: { )} {currentPath && uiSwitcher && uiSwitcher.uis.length > 0 && (
- +