* 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.
56 lines
1.8 KiB
TypeScript
56 lines
1.8 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 type { LucideIcon } from "lucide-react";
|
|
// plane ui
|
|
import { useTranslation } from "@plane/i18n";
|
|
import type { ISvgIcons } from "@plane/propel/icons";
|
|
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
|
// plane utils
|
|
import { cn } from "@plane/utils";
|
|
|
|
type Props = {
|
|
onChange: (value: number) => void;
|
|
value: number;
|
|
accessSpecifiers: {
|
|
key: number;
|
|
i18n_label: string;
|
|
icon: LucideIcon | React.FC<ISvgIcons>;
|
|
}[];
|
|
isMobile?: boolean;
|
|
};
|
|
|
|
export function AccessField(props: Props) {
|
|
const { onChange, value, accessSpecifiers, isMobile = false } = props;
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded-sm border-[1px] border-subtle p-1">
|
|
{accessSpecifiers.map((access, index) => {
|
|
const label = t(access.i18n_label);
|
|
return (
|
|
<Tooltip key={access.key} label={label} layout="stacked" disabled={isMobile}>
|
|
<button
|
|
type="button"
|
|
aria-label={label}
|
|
onClick={() => onChange(access.key)}
|
|
className={cn(
|
|
"relative flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-xs p-1 transition-all",
|
|
value === access.key ? "bg-layer-1" : "hover:bg-layer-1"
|
|
)}
|
|
tabIndex={2 + index}
|
|
>
|
|
<access.icon
|
|
className={cn("h-3.5 w-3.5 transition-all", value === access.key ? "text-primary" : "text-placeholder")}
|
|
strokeWidth={2}
|
|
/>
|
|
</button>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|