* refactor: migrate web @plane/propel Tooltip to @makeplane/propel Move all 144 Tooltip call sites in apps/web from the in-repo @plane/propel/tooltip to the published @makeplane/propel/components/tooltip. Prop mapping applied on every site: - tooltipContent -> label (string) - tooltipHeading -> folded into label + layout="stacked" - position -> side/align (top/center omitted; it is the published default) - isMobile -> disabled (OR'd with any existing disabled) - renderByDefault -> dropped; the in-repo Tooltip declared but never used it - className -> dropped, or mapped to sideOffset/alignOffset (3 sites) layout is "single" for short bounded copy and "stacked" wherever the label interpolates unbounded data (names, titles, URLs, joined lists) — "single" is whitespace-nowrap, so long labels would overflow. Behavior change: tooltips now open after the published 600ms default instead of the in-repo 200ms. delay is deliberately not set per site. ReactNode content: - Editor toolbars map name + <kbd>shortcut</kbd> onto the published shortcut prop - <br/> and fragment copy folds into one wrapping stacked string - stickies/action-bar rendered a full <StickyNote> hover preview, not a tooltip; it moves to PreviewCard/PreviewCardTrigger/PreviewCardContent Published label is a string, so DropdownButtonProps.tooltipContent, date-range's customTooltipContent, and the two rich-filters components narrow from ReactNode to string. rich-filters/root.tsx guards filterConfig.tooltipContent, which stays ReactNode in @plane/types. Folded labels use ?? "" where the source expression is nullable so they cannot render the string "undefined" where the in-repo tooltip rendered nothing. Zero @plane/propel/tooltip references remain under apps/web. * fix: address review findings on the Tooltip migration Accessibility (CodeRabbit): Base UI wires no aria-describedby or aria-labelledby between the tooltip trigger and its popup — verified by rendering the published Tooltip and inspecting the DOM, the trigger comes out as `<button data-base-ui-tooltip-trigger>` with no aria at all and the popup has role="tooltip" but no id. So a `label` never reaches assistive tech, and the four icon-only triggers announced as bare "button". Add an explicit aria-label at each: extended-project-sidebar, layout-switcher, layout-selection, and access-field. Pre-existing gap (the in-repo tooltip sat on the same unwired Base UI trigger), fixed here since the PR touches these sites. AccessField now requires i18n_label and drops the optional pre-i18n `label`, so the computed label is non-empty by construction and serves as both the tooltip copy and the accessible name. Its only caller already passes i18n_label for every specifier, which also retires the TODO. Correctness: inbox issue-properties interpolated a nullable name into a template literal, so a duplicate work item with no name rendered the literal "undefined"; pass the name with ?? "" instead. CodeQL useless conditional: drop `|| ""` from stickyId in action-bar — both call sites already sit behind a guard proving recentStickyId truthy, and StickyNote treats "" and undefined identically. React Doctor prefer-module-scope-static-value: hoist badgeContent to module scope as BADGE_CONTENT; it holds only static strings. React Doctor js-combine-iterations: collapse the filter/map chains to a single pass. The two layout switchers skip inside map instead of pre-filtering; LabelSummary reduces project labels into the selected names once rather than filter -> map -> join.
64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
/**
|
|
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
* See the LICENSE file for details.
|
|
*/
|
|
|
|
import { useTranslation } from "@plane/i18n";
|
|
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
|
import type { TBaseLayoutType } from "@plane/types";
|
|
import { cn } from "@plane/utils";
|
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
|
import { BASE_LAYOUTS } from "./constants";
|
|
|
|
type Props = {
|
|
layouts?: TBaseLayoutType[];
|
|
onChange: (layout: TBaseLayoutType) => void;
|
|
selectedLayout: TBaseLayoutType;
|
|
};
|
|
|
|
export function LayoutSwitcher(props: Props) {
|
|
const { layouts, onChange, selectedLayout } = props;
|
|
const { isMobile } = usePlatformOS();
|
|
const { t } = useTranslation();
|
|
|
|
const handleOnChange = (layoutKey: TBaseLayoutType) => {
|
|
if (selectedLayout !== layoutKey) {
|
|
onChange(layoutKey);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-1 rounded-md bg-layer-3 p-1">
|
|
{BASE_LAYOUTS.map((layout) => {
|
|
if (layouts && !layouts.includes(layout.key)) return null;
|
|
const Icon = layout.icon;
|
|
return (
|
|
<Tooltip key={layout.key} label={t(layout.label)} disabled={isMobile}>
|
|
<button
|
|
type="button"
|
|
aria-label={t(layout.label)}
|
|
className={cn(
|
|
"group grid h-5.5 w-7 place-items-center overflow-hidden rounded-sm transition-all hover:bg-layer-transparent-hover",
|
|
{
|
|
"bg-layer-transparent-active hover:bg-layer-transparent-active": selectedLayout === layout.key,
|
|
}
|
|
)}
|
|
onClick={() => handleOnChange(layout.key)}
|
|
>
|
|
<Icon
|
|
width={14}
|
|
height={14}
|
|
strokeWidth={2}
|
|
className={cn("size-3.5", {
|
|
"text-primary": selectedLayout === layout.key,
|
|
"text-secondary": selectedLayout !== layout.key,
|
|
})}
|
|
/>
|
|
</button>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|