fix(front): page header title overlap, Cmd+K on page layout pages & stable tooltip id (#22678)
## Summary
Three independent front-end fixes.
### 1. Settings page header title overlaps the breadcrumb
On settings detail pages (e.g. an app's logic function), a long centered
title visually overlapped the breadcrumb.
`PageCardHeader` renders the header as a CSS grid (`minmax(0, 1fr) auto
minmax(0, 1fr)`) and the centered title used `justify-self: center`.
With grid, `justify-self: center` sizes the item to its own content
width (up to its `max-width`) instead of to its grid track, so a long
title grew wider than the center track and spilled sideways over the
breadcrumb column — its `overflow: hidden` only clipped its own children
to that oversized box, not to the track.
Fix: let the centered title fill and shrink to its grid track so it
clips (with ellipsis) inside its own column instead of overflowing into
the breadcrumb.
- Center column: `auto` → `minmax(0, auto)` so it can shrink when space
is tight.
- Centered title: `justify-self: center` → `justify-self: stretch` +
`min-width: 0`.
### 2. Cmd+K does nothing on standalone page layout pages
On standalone page layout pages (`/page/:pageLayoutId`, used to render
app front components), the command menu shortcut (Cmd+K) did nothing.
Cmd+K is a global hotkey with a modifier, so it only runs when the
active focus-stack config has `enableGlobalHotkeysWithModifiers: true`.
`RecordIndexPage` and `RecordShowPage` explicitly reset the focus stack
to enable it, but `PageChangeEffect` had no case for
`AppPath.PageLayoutPage`, so the stack kept a stale config (commonly the
Settings config, which disables modifier hotkeys) and swallowed the
shortcut.
Fix: add a `PageLayoutPage` case in `PageChangeEffect` that resets the
focus stack with modifier hotkeys enabled (mirroring `RecordShowPage`),
plus a new `PageFocusId.PageLayoutPage` value.
### 3. Ever-changing / unstable tooltip element id
`OverflowingTextWithTooltip` built its element id from `title-id-${+new
Date()}`, so a new id (the current epoch time in ms) was generated on
every render — the id visibly kept increasing in the DOM. This is
unstable (the tooltip anchor `#id` churns on each render) and
collision-prone (two tooltips rendering in the same millisecond get the
same id, producing duplicate DOM ids and an ambiguous anchor).
Fix: derive the id from React's `useId()` so it is stable per instance
and unique. The colons `useId()` produces are stripped, since the id is
used inside a CSS selector (`anchorSelect={#${id}}`) where colons are
invalid.
## Test plan
- [ ] Open a settings detail page with a long title (e.g. an app logic
function named `maintain-account-team-member-name-on-created`) and
confirm the title no longer overlaps the breadcrumb, and truncates with
an ellipsis when space is tight.
- [ ] Navigate to a standalone page layout page (an app's
front-component page) and confirm Cmd+K opens the command menu,
including after coming from Settings.
- [ ] Inspect an overflowing title/tooltip in DevTools and confirm its
`id` stays stable across re-renders (no longer increments), and tooltips
still show on hover of truncated text.
This commit is contained in:
@@ -227,6 +227,22 @@ export const PageChangeEffect = () => {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case isMatchingLocation(location, AppPath.PageLayoutPage): {
|
||||
resetFocusStackToFocusItem({
|
||||
focusStackItem: {
|
||||
focusId: PageFocusId.PageLayoutPage,
|
||||
componentInstance: {
|
||||
componentType: FocusComponentType.PAGE,
|
||||
componentInstanceId: PageFocusId.PageLayoutPage,
|
||||
},
|
||||
globalHotkeysConfig: {
|
||||
enableGlobalHotkeysWithModifiers: true,
|
||||
enableGlobalHotkeysConflictingWithKeyboard: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case isMatchingLocation(location, AppPath.SignInUp): {
|
||||
resetFocusStackToFocusItem({
|
||||
focusStackItem: {
|
||||
|
||||
@@ -8,4 +8,5 @@ export enum PageFocusId {
|
||||
PlanRequired = 'plan-required',
|
||||
RecordShowPage = 'record-show-page',
|
||||
RecordIndex = 'record-index',
|
||||
PageLayoutPage = 'page-layout-page',
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const StyledHeader = styled.div<{ centerTitle?: boolean }>`
|
||||
display: grid;
|
||||
grid-template-columns: ${({ centerTitle }) =>
|
||||
centerTitle
|
||||
? 'minmax(0, 1fr) auto minmax(0, 1fr)'
|
||||
? 'minmax(0, 1fr) minmax(0, auto) minmax(0, 1fr)'
|
||||
: 'minmax(0, auto) minmax(0, 1fr)'};
|
||||
min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px;
|
||||
padding: 0 ${themeCssVariables.spacing[3]};
|
||||
@@ -63,8 +63,9 @@ const StyledTitle = styled.div<{ titleColor?: string }>`
|
||||
const StyledCenteredTitle = styled(StyledTitle)`
|
||||
grid-column: 2;
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
justify-self: stretch;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
||||
+8
-2
@@ -1,4 +1,10 @@
|
||||
import { type CSSProperties, type ReactNode, useRef, useState } from 'react';
|
||||
import {
|
||||
type CSSProperties,
|
||||
type ReactNode,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@@ -35,7 +41,7 @@ export const OverflowingTextWithTooltip = ({
|
||||
tooltipDelay = TooltipDelay.mediumDelay,
|
||||
alwaysShowTooltip = false,
|
||||
}: OverflowingTextWithTooltipProps) => {
|
||||
const textElementId = `title-id-${+new Date()}`;
|
||||
const textElementId = `title-id-${useId().replace(/:/g, '')}`;
|
||||
|
||||
const textRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user