* 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.
59 lines
1.8 KiB
TypeScript
59 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 { useEffect } from "react";
|
|
import { observer } from "mobx-react";
|
|
// plane imports
|
|
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
|
import { cn } from "@plane/utils";
|
|
// hooks
|
|
import { useLabel } from "@/hooks/store/use-label";
|
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
|
|
|
export type TReadonlyLabelsProps = {
|
|
className?: string;
|
|
hideIcon?: boolean;
|
|
value: string[];
|
|
placeholder?: string;
|
|
projectId: string | undefined;
|
|
workspaceSlug: string;
|
|
};
|
|
|
|
export const ReadonlyLabels = observer(function ReadonlyLabels(props: TReadonlyLabelsProps) {
|
|
const { className, value, projectId, workspaceSlug } = props;
|
|
|
|
const { getLabelById, fetchProjectLabels } = useLabel();
|
|
const { isMobile } = usePlatformOS();
|
|
const labels = value
|
|
.map((labelId) => getLabelById(labelId))
|
|
.filter((label): label is NonNullable<typeof label> => Boolean(label));
|
|
|
|
useEffect(() => {
|
|
if (projectId) {
|
|
fetchProjectLabels(workspaceSlug?.toString(), projectId);
|
|
}
|
|
}, [projectId, workspaceSlug]);
|
|
|
|
return (
|
|
<div className={cn("flex items-center gap-2 text-body-xs-regular", className)}>
|
|
{labels && (
|
|
<>
|
|
<Tooltip
|
|
label={`Labels: ${labels.map((l) => l?.name).join(", ")}`}
|
|
layout="stacked"
|
|
disabled={labels.length === 0 || isMobile}
|
|
>
|
|
<div className="flex h-full items-center gap-1 rounded-sm py-1 text-body-xs-bold">
|
|
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-accent-primary" />
|
|
<span>{value.length}</span>
|
|
<span>Labels</span>
|
|
</div>
|
|
</Tooltip>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
});
|