From ad5619564b691d8957dc0ede5de64caa9503822c Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 20 Nov 2025 14:25:04 +0100 Subject: [PATCH 1/5] TooltippedButton style according to the send button --- .../input/buttons/attach-button.tsx | 10 +---- .../input/buttons/cancel-button.tsx | 31 ++++++-------- .../mui-extras/tooltipped-button.tsx | 40 +++++++++++++++++-- .../mui-extras/tooltipped-icon-button.tsx | 10 +++-- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx b/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx index bbbdbe5a..9e790da2 100644 --- a/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx @@ -8,7 +8,7 @@ import AttachFileIcon from '@mui/icons-material/AttachFile'; import React from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; -import { TooltippedButton } from '../../mui-extras/tooltipped-button'; +import { TooltippedButton } from '../../mui-extras'; const ATTACH_BUTTON_CLASS = 'jp-chat-attach-button'; @@ -51,17 +51,9 @@ export function AttachButton( onClick={onclick} tooltip={tooltip} buttonProps={{ - size: 'small', - variant: 'text', title: tooltip, className: ATTACH_BUTTON_CLASS }} - sx={{ - width: '24px', - height: '24px', - minWidth: '24px', - color: 'gray' - }} > diff --git a/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx b/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx index d2689e78..b734045b 100644 --- a/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx @@ -4,10 +4,10 @@ */ import CloseIcon from '@mui/icons-material/Close'; -import { IconButton, Tooltip } from '@mui/material'; import React from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; +import { TooltippedButton } from '../../mui-extras'; const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button'; @@ -20,24 +20,17 @@ export function CancelButton( if (!props.model.cancel) { return <>; } - const tooltip = 'Cancel editing'; + const tooltip = 'Cancel edition'; return ( - - - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx index e2c714a3..9e83fb35 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx @@ -3,12 +3,43 @@ * Distributed under the terms of the Modified BSD License. */ -import { Button, ButtonProps, SxProps, TooltipProps } from '@mui/material'; +import { + Button, + ButtonOwnProps, + ButtonProps, + SxProps, + TooltipProps +} from '@mui/material'; import React from 'react'; import { ContrastingTooltip } from './contrasting-tooltip'; -const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap'; +export const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap'; + +export const DEFAULT_BUTTON_PROPS: Partial = { + size: 'small', + variant: 'contained' +}; + +export const DEFAULT_BUTTON_SX = { + backgroundColor: 'var(--jp-brand-color1)', + color: 'white', + minWidth: '24px', + width: '24px', + height: '24px', + borderRadius: '4px', + boxShadow: 'none', + lineHeight: 0, + '&:hover': { + backgroundColor: 'var(--jp-brand-color0)', + boxShadow: 'none' + }, + '&disabled': { + backgroundColor: 'var(--jp-border-color2)', + color: 'var(--jp-ui-font-color3)', + opacity: 0.5 + } +}; export type TooltippedButtonProps = { onClick: React.MouseEventHandler; @@ -76,15 +107,16 @@ export function TooltippedButton(props: TooltippedButtonProps): JSX.Element { */} diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx index c49632ce..e8e2ad10 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx @@ -8,8 +8,11 @@ import { IconButton, IconButtonProps, TooltipProps } from '@mui/material'; import React from 'react'; import { ContrastingTooltip } from './contrasting-tooltip'; - -const TOOLTIPPED_WRAP_CLASS = 'jp-chat-tooltipped-wrap'; +import { + DEFAULT_BUTTON_PROPS, + DEFAULT_BUTTON_SX, + TOOLTIPPED_WRAP_CLASS +} from './tooltipped-button'; export type TooltippedIconButtonProps = { onClick: () => unknown; @@ -73,12 +76,13 @@ export function TooltippedIconButton( */} Date: Thu, 20 Nov 2025 15:14:02 +0100 Subject: [PATCH 2/5] Use the TooltippedIconButton for all the default input toolbar buttons --- .../input/buttons/attach-button.tsx | 10 ++-- .../input/buttons/cancel-button.tsx | 10 ++-- .../input/buttons/save-edit-button.tsx | 35 +++++--------- .../components/input/buttons/send-button.tsx | 47 +++++-------------- .../components/input/buttons/stop-button.tsx | 47 +++++-------------- .../mui-extras/tooltipped-icon-button.tsx | 13 ++++- 6 files changed, 61 insertions(+), 101 deletions(-) diff --git a/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx b/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx index 9e790da2..0aaa6386 100644 --- a/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/attach-button.tsx @@ -8,7 +8,7 @@ import AttachFileIcon from '@mui/icons-material/AttachFile'; import React from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; -import { TooltippedButton } from '../../mui-extras'; +import { TooltippedIconButton } from '../../mui-extras'; const ATTACH_BUTTON_CLASS = 'jp-chat-attach-button'; @@ -47,15 +47,15 @@ export function AttachButton( }; return ( - - - + + ); } diff --git a/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx b/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx index b734045b..0f329783 100644 --- a/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/cancel-button.tsx @@ -7,7 +7,7 @@ import CloseIcon from '@mui/icons-material/Close'; import React from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; -import { TooltippedButton } from '../../mui-extras'; +import { TooltippedIconButton } from '../../mui-extras'; const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button'; @@ -22,15 +22,15 @@ export function CancelButton( } const tooltip = 'Cancel edition'; return ( - - - + + ); } diff --git a/packages/jupyter-chat/src/components/input/buttons/save-edit-button.tsx b/packages/jupyter-chat/src/components/input/buttons/save-edit-button.tsx index 74eb8c04..5a340c69 100644 --- a/packages/jupyter-chat/src/components/input/buttons/save-edit-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/save-edit-button.tsx @@ -4,10 +4,10 @@ */ import CheckIcon from '@mui/icons-material/Check'; -import { IconButton, Tooltip } from '@mui/material'; import React, { useEffect, useState } from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; +import { TooltippedIconButton } from '../../mui-extras'; const SAVE_EDIT_BUTTON_CLASS = 'jp-chat-save-edit-button'; @@ -50,26 +50,17 @@ export function SaveEditButton( } return ( - - - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/input/buttons/send-button.tsx b/packages/jupyter-chat/src/components/input/buttons/send-button.tsx index 96108191..d3ba7b8e 100644 --- a/packages/jupyter-chat/src/components/input/buttons/send-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/send-button.tsx @@ -4,10 +4,10 @@ */ import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; -import { Button, Tooltip } from '@mui/material'; import React, { useEffect, useState } from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; +import { TooltippedIconButton } from '../../mui-extras'; import { IInputModel, InputModel } from '../../../input-model'; const SEND_BUTTON_CLASS = 'jp-chat-send-button'; @@ -69,38 +69,17 @@ export function SendButton( } return ( - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/input/buttons/stop-button.tsx b/packages/jupyter-chat/src/components/input/buttons/stop-button.tsx index 4e0f9e16..e943780b 100644 --- a/packages/jupyter-chat/src/components/input/buttons/stop-button.tsx +++ b/packages/jupyter-chat/src/components/input/buttons/stop-button.tsx @@ -4,10 +4,10 @@ */ import StopIcon from '@mui/icons-material/Stop'; -import { Button, Tooltip } from '@mui/material'; import React, { useEffect, useState } from 'react'; import { InputToolbarRegistry } from '../toolbar-registry'; +import { TooltippedIconButton } from '../../mui-extras'; const STOP_BUTTON_CLASS = 'jp-chat-stop-button'; @@ -51,38 +51,17 @@ export function StopButton( } return ( - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx index e8e2ad10..b3fbeee4 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx @@ -4,7 +4,12 @@ */ import { classes } from '@jupyterlab/ui-components'; -import { IconButton, IconButtonProps, TooltipProps } from '@mui/material'; +import { + IconButton, + IconButtonProps, + SvgIconOwnProps, + TooltipProps +} from '@mui/material'; import React from 'react'; import { ContrastingTooltip } from './contrasting-tooltip'; @@ -21,6 +26,10 @@ export type TooltippedIconButtonProps = { className?: string; disabled?: boolean; placement?: TooltipProps['placement']; + /** + * The font size of the icon. By default it will be set to 'small'. + */ + fontSize?: SvgIconOwnProps['fontSize']; /** * The offset of the tooltip popup. * @@ -50,6 +59,8 @@ export type TooltippedIconButtonProps = { export function TooltippedIconButton( props: TooltippedIconButtonProps ): JSX.Element { + // Override the default icon font size from 'medium' to 'small' + props.children.props.fontSize = props.fontSize ?? 'small'; return ( Date: Thu, 20 Nov 2025 16:40:08 +0100 Subject: [PATCH 3/5] Use the TooltippedIconButton for message toolbar buttons --- .../src/components/messages/toolbar.tsx | 52 +++++++------------ .../mui-extras/tooltipped-button.tsx | 12 +++-- .../mui-extras/tooltipped-icon-button.tsx | 5 +- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/packages/jupyter-chat/src/components/messages/toolbar.tsx b/packages/jupyter-chat/src/components/messages/toolbar.tsx index b7fe0b9d..736cd242 100644 --- a/packages/jupyter-chat/src/components/messages/toolbar.tsx +++ b/packages/jupyter-chat/src/components/messages/toolbar.tsx @@ -5,9 +5,11 @@ // import EditIcon from '@mui/icons-material/Edit'; import DeleteIcon from '@mui/icons-material/Delete'; -import { Box, IconButton, Tooltip } from '@mui/material'; +import { Box } from '@mui/material'; import React from 'react'; +import { TooltippedIconButton } from '../mui-extras'; + const TOOLBAR_CLASS = 'jp-chat-toolbar'; /** @@ -18,43 +20,27 @@ export function MessageToolbar(props: MessageToolbar.IProps): JSX.Element { // if (props.edit !== undefined) { // const editButton = ( - // - // - // - // - // - // - // + // + // + // // ); // buttons.push(editButton); // } if (props.delete !== undefined) { const deleteButton = ( - - - - - - - + + + ); buttons.push(deleteButton); } diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx index 9e83fb35..e960c5bf 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx @@ -22,14 +22,17 @@ export const DEFAULT_BUTTON_PROPS: Partial = { }; export const DEFAULT_BUTTON_SX = { - backgroundColor: 'var(--jp-brand-color1)', - color: 'white', minWidth: '24px', width: '24px', height: '24px', + lineHeight: 0 +}; + +export const INPUT_TOOLBAR_BUTTON_SX = { + backgroundColor: 'var(--jp-brand-color1)', + color: 'white', borderRadius: '4px', boxShadow: 'none', - lineHeight: 0, '&:hover': { backgroundColor: 'var(--jp-brand-color0)', boxShadow: 'none' @@ -45,6 +48,7 @@ export type TooltippedButtonProps = { onClick: React.MouseEventHandler; tooltip: string; children: JSX.Element; + inputToolbar?: boolean; disabled?: boolean; placement?: TooltipProps['placement']; /** @@ -113,7 +117,7 @@ export function TooltippedButton(props: TooltippedButtonProps): JSX.Element { disabled={props.disabled} sx={{ ...DEFAULT_BUTTON_SX, - ...(props.disabled && { opacity: 0.5 }), + ...((props.inputToolbar ?? true) && INPUT_TOOLBAR_BUTTON_SX), ...props.sx }} aria-label={props['aria-label'] ?? props.tooltip} diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx index b3fbeee4..a78889d3 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-icon-button.tsx @@ -16,6 +16,7 @@ import { ContrastingTooltip } from './contrasting-tooltip'; import { DEFAULT_BUTTON_PROPS, DEFAULT_BUTTON_SX, + INPUT_TOOLBAR_BUTTON_SX, TOOLTIPPED_WRAP_CLASS } from './tooltipped-button'; @@ -24,6 +25,7 @@ export type TooltippedIconButtonProps = { tooltip: string; children: JSX.Element; className?: string; + inputToolbar?: boolean; disabled?: boolean; placement?: TooltipProps['placement']; /** @@ -93,8 +95,7 @@ export function TooltippedIconButton( disabled={props.disabled} sx={{ ...DEFAULT_BUTTON_SX, - marginLeft: '8px', - ...(props.disabled && { opacity: 0.5 }) + ...((props.inputToolbar ?? true) && INPUT_TOOLBAR_BUTTON_SX) }} aria-label={props['aria-label']} > From 48753a81b63b88b2ba8de025e105a87e93cc8de5 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 20 Nov 2025 17:03:07 +0100 Subject: [PATCH 4/5] Use the TooltippedIconButton in the code toolbar --- .../components/code-blocks/code-toolbar.tsx | 84 +++++++------------ .../components/code-blocks/copy-button.tsx | 33 +++----- .../mui-extras/tooltipped-button.tsx | 7 +- 3 files changed, 47 insertions(+), 77 deletions(-) diff --git a/packages/jupyter-chat/src/components/code-blocks/code-toolbar.tsx b/packages/jupyter-chat/src/components/code-blocks/code-toolbar.tsx index e2325601..e740a3b0 100644 --- a/packages/jupyter-chat/src/components/code-blocks/code-toolbar.tsx +++ b/packages/jupyter-chat/src/components/code-blocks/code-toolbar.tsx @@ -4,10 +4,11 @@ */ import { addAboveIcon, addBelowIcon } from '@jupyterlab/ui-components'; -import { Box, IconButton, Tooltip } from '@mui/material'; +import { Box } from '@mui/material'; import React, { useEffect, useState } from 'react'; import { CopyButton } from './copy-button'; +import { TooltippedIconButton } from '../mui-extras'; import { IActiveCellManager } from '../../active-cell-manager'; import { replaceCellIcon } from '../../icons'; import { IChatModel } from '../../model'; @@ -114,24 +115,15 @@ function InsertAboveButton(props: ToolbarButtonProps) { : 'Insert above active cell (no active cell)'; return ( - - - props.activeCellManager?.insertAbove(props.content)} - disabled={!props.activeCellAvailable} - aria-label={tooltip} - sx={{ - lineHeight: 0, - '&.Mui-disabled': { - opacity: 0.5 - } - }} - > - - - - + props.activeCellManager?.insertAbove(props.content)} + disabled={!props.activeCellAvailable} + inputToolbar={false} + > + + ); } @@ -141,24 +133,15 @@ function InsertBelowButton(props: ToolbarButtonProps) { : 'Insert below active cell (no active cell)'; return ( - - - props.activeCellManager?.insertBelow(props.content)} - aria-label={tooltip} - sx={{ - lineHeight: 0, - '&.Mui-disabled': { - opacity: 0.5 - } - }} - > - - - - + props.activeCellManager?.insertBelow(props.content)} + inputToolbar={false} + > + + ); } @@ -187,23 +170,14 @@ function ReplaceButton(props: ToolbarButtonProps) { }; return ( - - - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/code-blocks/copy-button.tsx b/packages/jupyter-chat/src/components/code-blocks/copy-button.tsx index 6fb67d1b..08cefe8a 100644 --- a/packages/jupyter-chat/src/components/code-blocks/copy-button.tsx +++ b/packages/jupyter-chat/src/components/code-blocks/copy-button.tsx @@ -3,10 +3,10 @@ * Distributed under the terms of the Modified BSD License. */ +import { copyIcon } from '@jupyterlab/ui-components'; import React, { useState, useCallback, useRef } from 'react'; -import { copyIcon } from '@jupyterlab/ui-components'; -import { IconButton, Tooltip } from '@mui/material'; +import { TooltippedIconButton } from '../mui-extras'; enum CopyStatus { None, @@ -61,23 +61,16 @@ export function CopyButton(props: CopyButtonProps): JSX.Element { const tooltip = COPYBTN_TEXT_BY_STATUS[copyStatus]; return ( - - - - - - - + + + ); } diff --git a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx index e960c5bf..dd516a2a 100644 --- a/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx +++ b/packages/jupyter-chat/src/components/mui-extras/tooltipped-button.tsx @@ -25,7 +25,10 @@ export const DEFAULT_BUTTON_SX = { minWidth: '24px', width: '24px', height: '24px', - lineHeight: 0 + lineHeight: 0, + '&:disabled': { + opacity: 0.5 + } }; export const INPUT_TOOLBAR_BUTTON_SX = { @@ -37,7 +40,7 @@ export const INPUT_TOOLBAR_BUTTON_SX = { backgroundColor: 'var(--jp-brand-color0)', boxShadow: 'none' }, - '&disabled': { + '&:disabled': { backgroundColor: 'var(--jp-border-color2)', color: 'var(--jp-ui-font-color3)', opacity: 0.5 From 88df7352f3e4ad3a88b8965531e4da945e1f0568 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 20 Nov 2025 17:45:20 +0100 Subject: [PATCH 5/5] Fix test --- ui-tests/tests/code-toolbar.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-tests/tests/code-toolbar.spec.ts b/ui-tests/tests/code-toolbar.spec.ts index e947060b..4817cd69 100644 --- a/ui-tests/tests/code-toolbar.spec.ts +++ b/ui-tests/tests/code-toolbar.spec.ts @@ -58,7 +58,7 @@ test.describe('#codeToolbar', () => { test('buttons should be disabled without notebook', async ({ page }) => { const chatPanel = await openChat(page, FILENAME); const message = chatPanel.locator('.jp-chat-message'); - const toolbarButtons = message.locator('.jp-chat-code-toolbar-item'); + const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button'); await sendMessage(page, FILENAME, MESSAGE); await expect(toolbarButtons).toHaveCount(4); @@ -73,7 +73,7 @@ test.describe('#codeToolbar', () => { }) => { const chatPanel = await openChat(page, FILENAME); const message = chatPanel.locator('.jp-chat-message'); - const toolbarButtons = message.locator('.jp-chat-code-toolbar-item'); + const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button'); await page.notebook.createNew(); @@ -88,7 +88,7 @@ test.describe('#codeToolbar', () => { }) => { const chatPanel = await openChat(page, FILENAME); const message = chatPanel.locator('.jp-chat-message'); - const toolbarButtons = message.locator('.jp-chat-code-toolbar-item'); + const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button'); const notebook = await page.notebook.createNew(); @@ -102,7 +102,7 @@ test.describe('#codeToolbar', () => { test('insert code above', async ({ page }) => { const chatPanel = await openChat(page, FILENAME); const message = chatPanel.locator('.jp-chat-message'); - const toolbarButtons = message.locator('.jp-chat-code-toolbar-item'); + const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button'); const notebook = await page.notebook.createNew(); @@ -123,7 +123,7 @@ test.describe('#codeToolbar', () => { test('insert code below', async ({ page }) => { const chatPanel = await openChat(page, FILENAME); const message = chatPanel.locator('.jp-chat-message'); - const toolbarButtons = message.locator('.jp-chat-code-toolbar-item'); + const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button'); const notebook = await page.notebook.createNew();