From 317d1f08583394f52d882eece3c2426ddecdab31 Mon Sep 17 00:00:00 2001 From: Uku Roio Date: Fri, 2 Oct 2026 11:10:37 +0300 Subject: [PATCH 1/2] fix(base-map-selection): always show title tooltip #956 --- .../base-map-selection/base-map-option.tsx | 37 ++------- .../base-map-selection.spec.tsx | 75 ++++--------------- 2 files changed, 20 insertions(+), 92 deletions(-) diff --git a/src/community/components/map-components/base-map-selection/base-map-option.tsx b/src/community/components/map-components/base-map-selection/base-map-option.tsx index a01a06041..4d8f8031e 100644 --- a/src/community/components/map-components/base-map-selection/base-map-option.tsx +++ b/src/community/components/map-components/base-map-selection/base-map-option.tsx @@ -1,10 +1,9 @@ import classNames from 'classnames'; -import { JSX, useEffect, useRef, useState } from 'react'; +import { JSX } from 'react'; import { Icon } from '../../../../tedi/components/base/icon/icon'; import Button from '../../../../tedi/components/buttons/button/button'; import { Tooltip } from '../../../../tedi/components/overlays/tooltip'; -import { useElementSize } from '../../../../tedi/helpers'; import styles from './base-map-selection.module.scss'; export type BaseMapOptionType = 'button' | 'historical' | 'selection'; @@ -55,8 +54,8 @@ export interface BaseMapOptionProps { disabled?: boolean; /** * Text shown in a tooltip. When set, an info icon is rendered in the middle of the - * option and hovering anywhere on the option reveals this text. A title too long to - * fit is shown above it in the same tooltip rather than in a second, competing one. + * option and hovering anywhere on the option reveals this text below the title, which + * the tooltip always shows. */ tooltipText?: string; /** @@ -85,18 +84,6 @@ export const BaseMapOption = (props: BaseMapOptionProps): JSX.Element => { tooltipType = 'info', } = props; - const titleRef = useRef(null); - const titleSize = useElementSize(titleRef); - const [isTruncated, setIsTruncated] = useState(false); - - useEffect(() => { - const node = titleRef.current; - - if (node) { - setIsTruncated(node.scrollWidth > node.clientWidth); - } - }, [titleSize, title]); - const handleSelect = () => { if (disabled) { return; @@ -136,28 +123,16 @@ export const BaseMapOption = (props: BaseMapOptionProps): JSX.Element => { /> )} -
- {title} -
+
{title}
); - if (!tooltipText && !isTruncated) { - return option; - } - return ( {option} - {tooltipText ? ( - <> - {isTruncated &&
{title}
} - {tooltipText} - - ) : ( - title - )} +
{title}
+ {tooltipText}
); diff --git a/src/community/components/map-components/base-map-selection/base-map-selection.spec.tsx b/src/community/components/map-components/base-map-selection/base-map-selection.spec.tsx index a45dbdfea..b31f33a45 100644 --- a/src/community/components/map-components/base-map-selection/base-map-selection.spec.tsx +++ b/src/community/components/map-components/base-map-selection/base-map-selection.spec.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen, within } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; // The `src/tedi` barrel transitively imports react-sticky-box (ESM-only), which Jest does not transform. @@ -14,12 +14,6 @@ jest.mock('../../../../tedi/helpers', () => ({ useBreakpoint: jest.fn(), })); -// `useElementSize` measures through a 20ms lodash debounce, so it lands a setState -// shortly after mount. Synchronous `fireEvent` tests finish first, but `userEvent` -// yields to the event loop - flush the debounce inside act() so that late update -// does not trip React's act(...) warning. -const settleElementSize = () => act(async () => void (await new Promise((resolve) => setTimeout(resolve, 25)))); - // The transparency controls take their accessible name from the label provider, so the // component must be wrapped the way a consuming app wraps it. const renderSelection = (props?: Partial>) => @@ -230,7 +224,6 @@ describe('BaseMapOption', () => { /> ); const option = screen.getByRole('button', { name: /Streets/ }); - await settleElementSize(); await user.click(option); option.focus(); @@ -265,7 +258,6 @@ describe('BaseMapOption', () => { /> ); const option = screen.getByRole('button', { name: /Streets/ }); - await settleElementSize(); await user.click(option); option.focus(); @@ -275,39 +267,26 @@ describe('BaseMapOption', () => { expect(onSelect).not.toHaveBeenCalled(); }); - describe('title truncation tooltip', () => { - const stubTitleWidths = (scrollWidth: number, clientWidth: number) => { - Object.defineProperty(HTMLElement.prototype, 'scrollWidth', { configurable: true, value: scrollWidth }); - Object.defineProperty(HTMLElement.prototype, 'clientWidth', { configurable: true, value: clientWidth }); - }; - - afterEach(() => { - stubTitleWidths(0, 0); - }); - - it('shows the full title in a tooltip on hover when the title is truncated', () => { - stubTitleWidths(200, 100); - const title = 'A Very Long Base Map Title'; - render(} />); + describe('title tooltip', () => { + it('shows the title in a tooltip on hover', () => { + render(} />); - expect(screen.getAllByText(title)).toHaveLength(1); + expect(screen.getAllByText('Streets')).toHaveLength(1); fireEvent.mouseEnter(screen.getByRole('button')); - expect(screen.getAllByText(title)).toHaveLength(2); + expect(screen.getAllByText('Streets')).toHaveLength(2); }); it('does not describe the trigger with a title-only tooltip, which would announce the title twice', () => { - stubTitleWidths(200, 100); - const title = 'A Very Long Base Map Title'; - render(} />); + render(} />); const option = screen.getByRole('button'); fireEvent.mouseEnter(option); // Visible in the tooltip, but not wired up as a description: the title already *is* // the button's accessible name. - expect(screen.getAllByText(title)).toHaveLength(2); + expect(screen.getAllByText('Streets')).toHaveLength(2); expect(option).not.toHaveAttribute('aria-describedby'); }); @@ -329,33 +308,19 @@ describe('BaseMapOption', () => { expect(document.getElementById(describedBy as string)).toHaveTextContent('Kaardikiht ei ole hetkel saadaval.'); }); - it('stays closed when the title fits and there is no tooltip text', () => { - stubTitleWidths(100, 100); - const title = 'Streets'; - render(} />); - - fireEvent.mouseEnter(screen.getByRole('button')); - - expect(screen.getAllByText(title)).toHaveLength(1); - }); - it('opens the tooltip on a disabled option too', () => { - stubTitleWidths(200, 100); - const title = 'A Very Long Base Map Title'; - render(} />); + render(} />); fireEvent.mouseEnter(screen.getByRole('button')); - expect(screen.getAllByText(title)).toHaveLength(2); + expect(screen.getAllByText('Streets')).toHaveLength(2); }); - it('merges a truncated title into the info tooltip instead of opening a second one', () => { - stubTitleWidths(200, 100); - const title = 'A Very Long Base Map Title'; + it('shows the title above the tooltip text in a single tooltip', () => { render( } /> @@ -364,18 +329,9 @@ describe('BaseMapOption', () => { fireEvent.mouseEnter(screen.getByRole('button')); const tooltip = screen.getByRole('tooltip'); - expect(tooltip).toHaveTextContent(title); + expect(tooltip).toHaveTextContent('Streets'); expect(tooltip).toHaveTextContent('Additional information'); - expect(screen.getAllByText(title)).toHaveLength(2); - }); - - it('does not render a tooltip when the title fits', () => { - stubTitleWidths(100, 100); - render(} />); - - fireEvent.mouseEnter(screen.getByRole('button')); - - expect(screen.getAllByText('Streets')).toHaveLength(1); + expect(screen.getAllByText('Streets')).toHaveLength(2); }); }); @@ -431,7 +387,6 @@ describe('BaseMapOption', () => { content={Streets} /> ); - await settleElementSize(); await user.tab(); @@ -450,7 +405,6 @@ describe('BaseMapOption', () => { content={Streets} /> ); - await settleElementSize(); await user.tab(); @@ -476,7 +430,6 @@ describe('BaseMapOption', () => { content={Streets} /> ); - await settleElementSize(); await user.tab(); await user.keyboard('{Enter}'); From f9882b92353ff8a4fcaefb2df2b6df49c8d24e12 Mon Sep 17 00:00:00 2001 From: Uku Roio Date: Fri, 2 Oct 2026 11:39:01 +0300 Subject: [PATCH 2/2] fix(base-map-selection): use correct color for disabled option title #956 --- .../base-map-selection.module.scss | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/community/components/map-components/base-map-selection/base-map-selection.module.scss b/src/community/components/map-components/base-map-selection/base-map-selection.module.scss index 09f189590..b2388cba9 100644 --- a/src/community/components/map-components/base-map-selection/base-map-selection.module.scss +++ b/src/community/components/map-components/base-map-selection/base-map-selection.module.scss @@ -89,7 +89,7 @@ width: 144px; height: 80px; - &:not(.tedi-base-map-selection--disabled):hover { + &:not(.tedi-base-map-selection--disabled, .tedi-base-map-selection--selected):hover { border-color: var(--general-border-brand); .tedi-base-map-selection__title { @@ -117,12 +117,6 @@ color: var(--map-historical-text-selected); background-color: var(--map-historical-text-background-selected); } - - &:not(.tedi-base-map-selection--disabled):hover { - .tedi-base-map-selection__title { - color: var(--map-historical-text-selected); - } - } } .tedi-base-map-selection__title { @@ -139,10 +133,13 @@ .tedi-base-map-selection--disabled { &.tedi-base-map-selection__wrapper { - .tedi-base-map-selection__content, - .tedi-base-map-selection__title { + .tedi-base-map-selection__content { opacity: 0.25; } + + .tedi-base-map-selection__title { + color: var(--general-text-disabled); + } } } @@ -190,6 +187,10 @@ flex-direction: column; border: 0; + &:focus-visible { + outline: none; + } + .tedi-base-map-selection__info { top: 38px;