fix(planner): cards over the map stay clear of the layer pill, and a wide tab bar shows full tab names

The base-layer pill sits in the map's bottom-left corner on purpose. The
place inspector, a tour's detail and the day panel are centred between the
sidebars and were assumed never to reach that corner, but with both
sidebars open in a 1280 window they do, and the pill covered "Remove from
Day" and Edit. These cards now keep their left edge clear of the pill: a
narrow map moves the card right of it, a wide map keeps it centred as
before. The pill itself does not move, and the phone sheets are unchanged.

The Bookings tab showed "Book" at every width, which read as cut off. The
short form now only shows where the bar is tight; from xl up the tab shows
its full name.

Refs #2604
This commit is contained in:
Maurice
2026-10-12 15:46:09 +02:00
parent cb04ebe6d2
commit 125649ce1b
10 changed files with 119 additions and 14 deletions
+5 -2
View File
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react'
import { X } from 'lucide-react'
import { NEUTRAL_TINT } from './DialogShell'
import { Tooltip } from './Tooltip'
import { centredMapCardBox } from '../Map/centredMapCard'
interface DetailShellProps {
header: ReactNode
@@ -11,6 +12,8 @@ interface DetailShellProps {
closeLabel: string
leftWidth?: number
rightWidth?: number
/** Over the desktop map: keep clear of the base-layer pill in its bottom-left corner. */
clearMapPill?: boolean
/** The head band's background; the neutral accent wash when left out. */
tint?: string
closeButtonClassName?: string
@@ -30,6 +33,7 @@ export default function DetailShell({
closeLabel,
leftWidth = 0,
rightWidth = 0,
clearMapPill = false,
tint = NEUTRAL_TINT,
closeButtonClassName = '',
testId,
@@ -39,9 +43,8 @@ export default function DetailShell({
style={{
position: 'absolute',
bottom: 20,
left: `calc(${leftWidth}px + (100% - ${leftWidth}px - ${rightWidth}px) / 2)`,
...centredMapCardBox(leftWidth, rightWidth, { clearPill: clearMapPill }),
transform: 'translateX(-50%)',
width: `min(800px, calc(100% - ${leftWidth}px - ${rightWidth}px - 32px))`,
zIndex: 50,
fontFamily: 'var(--font-system)',
}}
@@ -1,5 +1,5 @@
import { render, waitFor, act } from '../../../tests/helpers/render';
import SlidingTabs from './SlidingTabs';
import SlidingTabs, { TabLabel } from './SlidingTabs';
const TABS = [
{ id: 'a', label: 'Alpha' },
@@ -106,3 +106,17 @@ describe('SlidingTabs', () => {
expect(pill()!.style.width).toBe('90px');
});
});
describe('TabLabel', () => {
it('shows the full name in a wide window and the short form only where the bar is tight (#2604)', () => {
const { container } = render(<TabLabel label="Bookings" short="Book" />);
expect(container.querySelector('[class~="xl:inline"]')).toHaveTextContent('Bookings');
expect(container.querySelector('[class~="xl:hidden"]')).toHaveTextContent('Book');
});
it('a label without a short form has nothing to swap', () => {
const { container } = render(<TabLabel label="Lists" short="Lists" />);
expect(container.textContent).toBe('Lists');
expect(container.querySelector('[class~="xl:hidden"]')).toBeNull();
});
});
@@ -162,4 +162,19 @@ export function SlidingTabs<T extends string>({
)
}
/**
* A tab label that also has a short form: icon only on a phone, the short form
* where the bar is tight, the full name from xl up. A wide window has the room,
* and an abbreviation there ("Book" for Bookings) read as cut off (#2604).
*/
export function TabLabel({ label, short }: { label: string; short?: string }): React.ReactElement {
if (!short || short === label) return <span className="hidden sm:inline">{label}</span>
return (
<>
<span className="hidden sm:inline xl:hidden">{short}</span>
<span className="hidden xl:inline">{label}</span>
</>
)
}
export default SlidingTabs