[WEB-5478] chore: fix types (#8155)

This commit is contained in:
Aaron 2025-11-21 21:52:37 +07:00 committed by GitHub
parent 5cfb9538df
commit 0f4309659a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 369 additions and 240 deletions

View file

@ -51,8 +51,7 @@ const defaultFromData: TFormData = {
is_telemetry_enabled: true,
};
export function InstanceSetupForm(props) {
const {} = props;
export function InstanceSetupForm() {
// search params
const searchParams = useSearchParams();
const firstNameParam = searchParams.get("first_name") || undefined;

View file

@ -104,5 +104,3 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB
</div>
);
});
KanbanIssueBlock.displayName = "KanbanIssueBlock";

View file

@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
import type { AnalyticsTab } from "@plane/types";
import { Tabs } from "@plane/ui";
import type { TabItem } from "@plane/ui";
// components
@ -44,9 +45,7 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
const pageTitle = currentWorkspace?.name
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
: undefined;
const ANALYTICS_TABS = useMemo(function ANALYTICS_TABS() {
return getAnalyticsTabs(t);
});
const ANALYTICS_TABS = useMemo<AnalyticsTab[]>(() => getAnalyticsTabs(t), [t]);
const tabs: TabItem[] = useMemo(
() =>
ANALYTICS_TABS.map((tab) => ({

View file

@ -1,9 +1,9 @@
import { useMemo } from "react";
import { observer } from "mobx-react";
import { useTheme } from "next-themes";
import { Disclosure } from "@headlessui/react";
// plane imports
import { useTranslation } from "@plane/i18n";
import type { ICycle } from "@plane/types";
import { Row } from "@plane/ui";
// assets
import darkActiveCycleAsset from "@/app/assets/empty-state/cycle/active-dark.webp?url";
@ -27,6 +27,69 @@ interface IActiveCycleDetails {
showHeader?: boolean;
}
type ActiveCyclesComponentProps = {
cycleId: string | null | undefined;
activeCycle: ICycle | null;
activeCycleResolvedPath: string;
workspaceSlug: string;
projectId: string;
handleFiltersUpdate: (filters: any) => void;
cycleIssueDetails?: ActiveCycleIssueDetails | { nextPageResults: boolean };
};
const ActiveCyclesComponent = observer(function ActiveCyclesComponent({
cycleId,
activeCycle,
activeCycleResolvedPath,
workspaceSlug,
projectId,
handleFiltersUpdate,
cycleIssueDetails,
}: ActiveCyclesComponentProps) {
const { t } = useTranslation();
if (!cycleId || !activeCycle) {
return (
<DetailedEmptyState
title={t("project_cycles.empty_state.active.title")}
description={t("project_cycles.empty_state.active.description")}
assetPath={activeCycleResolvedPath}
/>
);
}
return (
<div className="flex flex-col border-b border-custom-border-200">
<CyclesListItem
key={cycleId}
cycleId={cycleId}
workspaceSlug={workspaceSlug}
projectId={projectId}
className="!border-b-transparent"
/>
<Row className="bg-custom-background-100 pt-3 pb-6">
<div className="grid grid-cols-1 bg-custom-background-100 gap-3 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress
handleFiltersUpdate={handleFiltersUpdate}
projectId={projectId}
workspaceSlug={workspaceSlug}
cycle={activeCycle}
/>
<ActiveCycleProductivity workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} />
<ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={cycleId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
</div>
</Row>
</div>
);
});
export const ActiveCycleRoot = observer(function ActiveCycleRoot(props: IActiveCycleDetails) {
const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props;
// theme hook
@ -45,51 +108,6 @@ export const ActiveCycleRoot = observer(function ActiveCycleRoot(props: IActiveC
cycleIssueDetails,
} = useCyclesDetails({ workspaceSlug, projectId, cycleId });
const ActiveCyclesComponent = useMemo(function ActiveCyclesComponent() {
return (
<>
{!cycleId || !activeCycle ? (
<DetailedEmptyState
title={t("project_cycles.empty_state.active.title")}
description={t("project_cycles.empty_state.active.description")}
assetPath={activeCycleResolvedPath}
/>
) : (
<div className="flex flex-col border-b border-custom-border-200">
{cycleId && (
<CyclesListItem
key={cycleId}
cycleId={cycleId}
workspaceSlug={workspaceSlug}
projectId={projectId}
className="!border-b-transparent"
/>
)}
<Row className="bg-custom-background-100 pt-3 pb-6">
<div className="grid grid-cols-1 bg-custom-background-100 gap-3 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress
handleFiltersUpdate={handleFiltersUpdate}
projectId={projectId}
workspaceSlug={workspaceSlug}
cycle={activeCycle}
/>
<ActiveCycleProductivity workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} />
<ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={cycleId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails as ActiveCycleIssueDetails}
/>
</div>
</Row>
</div>
)}
</>
);
});
return (
<>
{showHeader ? (
@ -99,12 +117,30 @@ export const ActiveCycleRoot = observer(function ActiveCycleRoot(props: IActiveC
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 cursor-pointer">
<CycleListGroupHeader title={t("project_cycles.active_cycle.label")} type="current" isExpanded={open} />
</Disclosure.Button>
<Disclosure.Panel>{ActiveCyclesComponent}</Disclosure.Panel>
<Disclosure.Panel>
<ActiveCyclesComponent
cycleId={cycleId}
activeCycle={activeCycle}
activeCycleResolvedPath={activeCycleResolvedPath}
workspaceSlug={workspaceSlug}
projectId={projectId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
</Disclosure.Panel>
</>
)}
</Disclosure>
) : (
<>{ActiveCyclesComponent}</>
<ActiveCyclesComponent
cycleId={cycleId}
activeCycle={activeCycle}
activeCycleResolvedPath={activeCycleResolvedPath}
workspaceSlug={workspaceSlug}
projectId={projectId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails}
/>
)}
</>
);

View file

@ -31,8 +31,8 @@ export const CommentQuickActions = observer(function CommentQuickActions(props:
// translation
const { t } = useTranslation();
const MENU_ITEMS = useMemo(function MENU_ITEMS() {
return [
const MENU_ITEMS = useMemo<TContextMenuItem[]>(
() => [
{
key: "edit",
action: setEditMode,
@ -70,8 +70,9 @@ export const CommentQuickActions = observer(function CommentQuickActions(props:
icon: Trash2,
shouldRender: canDelete,
},
];
});
],
[t, setEditMode, canEdit, showCopyLinkOption, activityOperations, comment, showAccessSpecifier, canDelete]
);
return (
<CustomMenu ellipsis closeOnSelect>

View file

@ -79,5 +79,3 @@ export const BreadcrumbLink = observer(function BreadcrumbLink(props: Props) {
return <ItemWrapper {...itemWrapperProps}>{content}</ItemWrapper>;
});
BreadcrumbLink.displayName = "BreadcrumbLink";

View file

@ -21,5 +21,3 @@ export const MultipleSelectGroup = observer(function MultipleSelectGroup(props:
return <>{children(helpers)}</>;
});
MultipleSelectGroup.displayName = "MultipleSelectGroup";

View file

@ -44,7 +44,7 @@ export type ActiveCycleStatsProps = {
cycle: ICycle | null;
cycleId?: string | null;
handleFiltersUpdate: (conditions: TWorkItemFilterCondition[]) => void;
cycleIssueDetails: ActiveCycleIssueDetails;
cycleIssueDetails?: ActiveCycleIssueDetails | { nextPageResults: boolean };
};
export const ActiveCycleStats = observer(function ActiveCycleStats(props: ActiveCycleStatsProps) {

View file

@ -32,7 +32,7 @@ type Props = {
};
export const CalendarIssueBlock = observer(
forwardRef<HTMLAnchorElement, Props>((props, ref) => {
forwardRef(function CalendarIssueBlock(props: Props, ref: React.ForwardedRef<HTMLAnchorElement>) {
const { issue, quickActions, isDragging = false, isEpic = false } = props;
// states
const [isMenuActive, setIsMenuActive] = useState(false);

View file

@ -299,5 +299,3 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB
</>
);
});
KanbanIssueBlock.displayName = "KanbanIssueBlock";

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import type { Placement } from "@popperjs/core";
import { observer } from "mobx-react";
// plane helpers
@ -36,6 +36,126 @@ export interface IIssuePropertyLabels {
fullHeight?: boolean;
}
type NoLabelProps = {
isMobile: boolean;
noLabelBorder: boolean;
fullWidth: boolean;
placeholderText?: string;
};
const NoLabel = observer(function NoLabel({ isMobile, noLabelBorder, fullWidth, placeholderText }: NoLabelProps) {
const { t } = useTranslation();
return (
<Tooltip
position="top"
tooltipHeading={t("common.labels")}
tooltipContent="None"
isMobile={isMobile}
renderByDefault={false}
>
<div
className={cn(
"flex h-full items-center justify-center gap-2 rounded px-2.5 py-1 text-xs hover:bg-custom-background-80",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300",
fullWidth && "w-full"
)}
>
<LabelPropertyIcon className="h-3.5 w-3.5" />
{placeholderText}
</div>
</Tooltip>
);
});
type LabelSummaryProps = {
isMobile: boolean;
fullWidth: boolean;
noLabelBorder: boolean;
disabled?: boolean;
projectLabels: IIssueLabel[];
value: string[];
};
function LabelSummary({ isMobile, fullWidth, noLabelBorder, disabled, projectLabels, value }: LabelSummaryProps) {
const { t } = useTranslation();
return (
<div
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center rounded px-2.5 text-xs",
fullWidth && "w-full",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300",
disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
>
<Tooltip
isMobile={isMobile}
position="top"
tooltipHeading={t("common.labels")}
tooltipContent={projectLabels
?.filter((l) => value.includes(l?.id))
.map((l) => l?.name)
.join(", ")}
renderByDefault={false}
>
<div className="flex h-full items-center gap-1.5 text-custom-text-200">
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" />
{`${value.length} Labels`}
</div>
</Tooltip>
</div>
);
}
type LabelItemProps = {
label: IIssueLabel;
isMobile: boolean;
renderByDefault: boolean;
disabled?: boolean;
fullWidth: boolean;
noLabelBorder: boolean;
};
const LabelItem = observer(function LabelItem({
label,
isMobile,
renderByDefault,
disabled,
fullWidth,
noLabelBorder,
}: LabelItemProps) {
const { t } = useTranslation();
return (
<Tooltip
position="top"
tooltipHeading={t("common.labels")}
tooltipContent={label?.name ?? ""}
isMobile={isMobile}
renderByDefault={renderByDefault}
>
<div
className={cn(
"flex overflow-hidden justify-center hover:bg-custom-background-80 max-w-full h-full flex-shrink-0 items-center rounded px-2.5 text-xs",
!disabled && "cursor-pointer",
fullWidth && "w-full",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300"
)}
>
<div className="flex max-w-full items-center gap-1.5 overflow-hidden text-custom-text-200">
<span
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: label?.color ?? "#000000",
}}
/>
<div className="line-clamp-1 inline-block w-auto max-w-[200px] truncate">{label?.name}</div>
</div>
</div>
</Tooltip>
);
});
export const IssuePropertyLabels = observer(function IssuePropertyLabels(props: IIssuePropertyLabels) {
const {
projectId,
@ -54,8 +174,6 @@ export const IssuePropertyLabels = observer(function IssuePropertyLabels(props:
fullWidth = false,
fullHeight = false,
} = props;
// i18n
const { t } = useTranslation();
// states
const [isOpen, setIsOpen] = useState(false);
// refs
@ -83,91 +201,6 @@ export const IssuePropertyLabels = observer(function IssuePropertyLabels(props:
let projectLabels: IIssueLabel[] = defaultOptions as IIssueLabel[];
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
const NoLabel = useMemo(function NoLabel() {
return (
<Tooltip
position="top"
tooltipHeading={t("common.labels")}
tooltipContent="None"
isMobile={isMobile}
renderByDefault={false}
>
<div
className={cn(
"flex h-full items-center justify-center gap-2 rounded px-2.5 py-1 text-xs hover:bg-custom-background-80",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300",
fullWidth && "w-full"
)}
>
<LabelPropertyIcon className="h-3.5 w-3.5" />
{placeholderText}
</div>
</Tooltip>
);
});
const LabelSummary = useMemo(function LabelSummary() {
return (
<div
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center rounded px-2.5 text-xs",
fullWidth && "w-full",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300",
disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
>
<Tooltip
isMobile={isMobile}
position="top"
tooltipHeading={t("common.labels")}
tooltipContent={projectLabels
?.filter((l) => value.includes(l?.id))
.map((l) => l?.name)
.join(", ")}
renderByDefault={false}
>
<div className="flex h-full items-center gap-1.5 text-custom-text-200">
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" />
{`${value.length} Labels`}
</div>
</Tooltip>
</div>
);
});
const LabelItem = useCallback(function LabelItem({ label }: { label: IIssueLabel }) {
return (
<Tooltip
key={label.id}
position="top"
tooltipHeading={t("common.labels")}
tooltipContent={label?.name ?? ""}
isMobile={isMobile}
renderByDefault={renderByDefault}
>
<div
key={label?.id}
className={cn(
"flex overflow-hidden justify-center hover:bg-custom-background-80 max-w-full h-full flex-shrink-0 items-center rounded px-2.5 text-xs",
!disabled && "cursor-pointer",
fullWidth && "w-full",
noLabelBorder ? "rounded-none" : "border-[0.5px] border-custom-border-300"
)}
>
<div className="flex max-w-full items-center gap-1.5 overflow-hidden text-custom-text-200">
<span
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: label?.color ?? "#000000",
}}
/>
<div className="line-clamp-1 inline-block w-auto max-w-[200px] truncate">{label?.name}</div>
</div>
</div>
</Tooltip>
);
});
return (
<>
{value.length > 0 ? (
@ -185,7 +218,16 @@ export const IssuePropertyLabels = observer(function IssuePropertyLabels(props:
hideDropdownArrow={hideDropdownArrow}
fullWidth={fullWidth}
fullHeight={fullHeight}
label={<LabelItem label={label} />}
label={
<LabelItem
label={label}
isMobile={isMobile}
renderByDefault={renderByDefault}
disabled={disabled}
fullWidth={fullWidth}
noLabelBorder={noLabelBorder}
/>
}
/>
))
) : (
@ -198,7 +240,16 @@ export const IssuePropertyLabels = observer(function IssuePropertyLabels(props:
placement={placement}
fullWidth={fullWidth}
fullHeight={fullHeight}
label={LabelSummary}
label={
<LabelSummary
isMobile={isMobile}
fullWidth={fullWidth}
noLabelBorder={noLabelBorder}
disabled={disabled}
projectLabels={projectLabels}
value={value}
/>
}
/>
)
) : (
@ -211,7 +262,14 @@ export const IssuePropertyLabels = observer(function IssuePropertyLabels(props:
placement={placement}
fullWidth={fullWidth}
fullHeight={fullHeight}
label={NoLabel}
label={
<NoLabel
isMobile={isMobile}
noLabelBorder={noLabelBorder}
fullWidth={fullWidth}
placeholderText={placeholderText}
/>
}
/>
)}
</>

View file

@ -33,7 +33,10 @@ const defaultValues: Partial<IIssueLabel> = {
};
export const CreateUpdateLabelInline = observer(
forwardRef<HTMLDivElement, TCreateUpdateLabelInlineProps>(function CreateUpdateLabelInline(props, ref) {
forwardRef(function CreateUpdateLabelInline(
props: TCreateUpdateLabelInlineProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
const { labelForm, setLabelForm, isUpdating, labelOperationsCallbacks, labelToUpdate, onClose } = props;
// form info
const {

View file

@ -1,5 +1,4 @@
import type { FC } from "react";
import { useEffect, useMemo, useRef } from "react";
import { useEffect, useRef } from "react";
// plane imports
import type { IWorkspaceMemberInvitation } from "@plane/types";
import { EOnboardingSteps } from "@plane/types";
@ -16,8 +15,25 @@ type Props = {
handleStepChange: (step: EOnboardingSteps, skipInvites?: boolean) => void;
};
function OnboardingStepContent({ currentStep, invitations, handleStepChange }: Props) {
switch (currentStep) {
case EOnboardingSteps.PROFILE_SETUP:
return <ProfileSetupStep handleStepChange={handleStepChange} />;
case EOnboardingSteps.ROLE_SETUP:
return <RoleSetupStep handleStepChange={handleStepChange} />;
case EOnboardingSteps.USE_CASE_SETUP:
return <UseCaseSetupStep handleStepChange={handleStepChange} />;
case EOnboardingSteps.WORKSPACE_CREATE_OR_JOIN:
return <WorkspaceSetupStep invitations={invitations ?? []} handleStepChange={handleStepChange} />;
case EOnboardingSteps.INVITE_MEMBERS:
return <InviteTeamStep handleStepChange={handleStepChange} />;
default:
return null;
}
}
export function OnboardingStepRoot(props: Props) {
const { currentStep, invitations, handleStepChange } = props;
const { currentStep } = props;
// ref for the scrollable container
const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -31,24 +47,12 @@ export function OnboardingStepRoot(props: Props) {
}
}, [currentStep]);
// memoized step component mapping
const stepComponents = useMemo(
() => ({
[EOnboardingSteps.PROFILE_SETUP]: <ProfileSetupStep handleStepChange={handleStepChange} />,
[EOnboardingSteps.ROLE_SETUP]: <RoleSetupStep handleStepChange={handleStepChange} />,
[EOnboardingSteps.USE_CASE_SETUP]: <UseCaseSetupStep handleStepChange={handleStepChange} />,
[EOnboardingSteps.WORKSPACE_CREATE_OR_JOIN]: (
<WorkspaceSetupStep invitations={invitations ?? []} handleStepChange={handleStepChange} />
),
[EOnboardingSteps.INVITE_MEMBERS]: <InviteTeamStep handleStepChange={handleStepChange} />,
}),
[handleStepChange, invitations]
);
return (
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto">
<div className="flex items-center justify-center min-h-full p-8">
<div className="w-full max-w-[24rem]">{stepComponents[currentStep]} </div>
<div className="w-full max-w-[24rem]">
<OnboardingStepContent {...props} />
</div>
</div>
</div>
);

View file

@ -87,7 +87,7 @@ export const PageActions = observer(function PageActions(props: Props) {
canCurrentUserMovePage,
} = page;
// menu items
const MENU_ITEMS = useMemo(function MENU_ITEMS() {
const MENU_ITEMS = useMemo(() => {
const menuItems: (TContextMenuItem & { key: TPageActions })[] = [
{
key: "toggle-lock",
@ -175,13 +175,26 @@ export const PageActions = observer(function PageActions(props: Props) {
menuItems.push(...extraOptions);
}
return menuItems;
});
}, [
extraOptions,
is_locked,
canCurrentUserLockPage,
access,
canCurrentUserChangeAccess,
archived_at,
canCurrentUserDuplicatePage,
canCurrentUserArchivePage,
canCurrentUserDeletePage,
canCurrentUserMovePage,
isMovePageEnabled,
pageOperations,
]);
// arrange options
const arrangedOptions = useMemo(
const arrangedOptions = useMemo<(TContextMenuItem & { key: TPageActions })[]>(
() =>
optionsOrder
.map((key) => MENU_ITEMS.find((item) => item.key === key))
.filter((item) => !!item) as (TContextMenuItem & { key: TPageActions })[],
.filter((item): item is TContextMenuItem & { key: TPageActions } => !!item),
[optionsOrder, MENU_ITEMS]
);

View file

@ -43,8 +43,8 @@ export const PageOptionsDropdown = observer(function PageOptionsDropdown(props:
// query params
const { updateQueryParams } = useQueryParams();
// menu items list
const EXTRA_MENU_OPTIONS = useMemo(function EXTRA_MENU_OPTIONS() {
return [
const EXTRA_MENU_OPTIONS = useMemo<(TContextMenuItem & { key: TPageActions })[]>(
() => [
{
key: "full-screen",
action: () => handleFullWidth(!isFullWidth),
@ -106,8 +106,19 @@ export const PageOptionsDropdown = observer(function PageOptionsDropdown(props:
icon: ArrowUpToLine,
shouldRender: true,
},
];
});
],
[
handleFullWidth,
isFullWidth,
handleStickyToolbar,
isStickyToolbarEnabled,
isContentEditable,
editorRef,
updateQueryParams,
router,
setIsExportModalOpen,
]
);
return (
<>

View file

@ -1,9 +1,7 @@
import { range } from "lodash-es";
import { Loader } from "@plane/ui";
export function PageLoader(props) {
const {} = props;
export function PageLoader() {
return (
<div className="relative w-full h-full flex flex-col">
<div className="px-3 border-b border-custom-border-100 py-3">

View file

@ -1,4 +1,4 @@
import { useEffect, useState, useMemo } from "react";
import { useEffect, useState } from "react";
import { TwitterPicker } from "react-color";
import { Button } from "@plane/propel/button";
import type { IState } from "@plane/types";
@ -12,6 +12,17 @@ type TStateForm = {
buttonTitle: string;
};
function PopoverButton({ color }: { color?: string }) {
return (
<div
className="group inline-flex items-center text-base font-medium focus:outline-none h-5 w-5 rounded transition-all"
style={{
backgroundColor: color ?? "black",
}}
/>
);
}
export function StateForm(props: TStateForm) {
const { data, onSubmit, onCancel, buttonDisabled, buttonTitle } = props;
// states
@ -45,22 +56,11 @@ export function StateForm(props: TStateForm) {
}
};
const PopoverButton = useMemo(function PopoverButton() {
return (
<div
className="group inline-flex items-center text-base font-medium focus:outline-none h-5 w-5 rounded transition-all"
style={{
backgroundColor: formData?.color ?? "black",
}}
/>
);
});
return (
<div className="relative flex space-x-2 bg-custom-background-100 p-3 rounded">
{/* color */}
<div className="flex-shrink-0 h-full mt-2">
<Popover button={PopoverButton} panelClassName="mt-4 -ml-3">
<Popover button={<PopoverButton color={formData?.color} />} panelClassName="mt-4 -ml-3">
<TwitterPicker color={formData?.color} onChange={(value) => handleFormData("color", value.hex)} />
</Popover>
</div>

View file

@ -119,7 +119,7 @@ export type AppSidebarItemComponent = React.FC<AppSidebarItemProps> & {
Button: React.FC<AppSidebarButtonItemProps>;
};
function AppSidebarItem({ variant = "link", item }) {
function AppSidebarItem({ variant = "link", item }: AppSidebarItemProps) {
if (!item) return null;
const { icon, isActive, label, href, onClick, disabled } = item;

View file

@ -33,27 +33,27 @@ interface Props {
options?: any;
}
function HeadingPrimary({ children }) {
function HeadingPrimary({ children }: { children: React.ReactNode }) {
return <h1 className="text-lg font-semibold text-custom-text-100">{children}</h1>;
}
function HeadingSecondary({ children }) {
function HeadingSecondary({ children }: { children: React.ReactNode }) {
return <h3 className="text-base font-semibold text-custom-text-100">{children}</h3>;
}
function Paragraph({ children }) {
function Paragraph({ children }: { children: React.ReactNode }) {
return <p className="text-sm text-custom-text-200">{children}</p>;
}
function OrderedList({ children }) {
function OrderedList({ children }: { children: React.ReactNode }) {
return <ol className="mb-4 ml-8 list-decimal text-sm text-custom-text-200">{children}</ol>;
}
function UnorderedList({ children }) {
function UnorderedList({ children }: { children: React.ReactNode }) {
return <ul className="mb-4 ml-8 list-disc text-sm text-custom-text-200">{children}</ul>;
}
function Link({ href, children }) {
function Link({ href, children }: CustomComponentProps) {
return (
<a href={href} className="underline hover:no-underline" target="_blank" rel="noopener noreferrer">
{children}

View file

@ -60,51 +60,51 @@ export const useRealtimePageEvents = ({
[getUserDetails]
);
const ACTION_HANDLERS = useMemo(function ACTION_HANDLERS() {
return {
archived: ({ pageIds, data }) => {
const ACTION_HANDLERS = useMemo(
() => ({
archived: ({ pageIds, data }: { pageIds: string[]; data: EventToPayloadMap["archived"] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.archive({ archived_at: data.archived_at, shouldSync: false });
});
},
unarchived: ({ pageIds }) => {
unarchived: ({ pageIds }: { pageIds: string[] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.restore({ shouldSync: false });
});
},
locked: ({ pageIds }) => {
locked: ({ pageIds }: { pageIds: string[] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.lock({ shouldSync: false, recursive: false });
});
},
unlocked: ({ pageIds }) => {
unlocked: ({ pageIds }: { pageIds: string[] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.unlock({ shouldSync: false, recursive: false });
});
},
"made-public": ({ pageIds }) => {
"made-public": ({ pageIds }: { pageIds: string[] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.makePublic({ shouldSync: false });
});
},
"made-private": ({ pageIds }) => {
"made-private": ({ pageIds }: { pageIds: string[] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) pageItem.makePrivate({ shouldSync: false });
});
},
deleted: ({ pageIds, data }) => {
deleted: ({ pageIds, data }: { pageIds: string[]; data: EventToPayloadMap["deleted"] }) => {
pageIds.forEach((pageId) => {
const pageItem = getPageById(pageId);
if (pageItem) {
@ -123,7 +123,7 @@ export const useRealtimePageEvents = ({
});
},
property_updated: ({ pageIds, data }) => {
property_updated: ({ pageIds, data }: { pageIds: string[]; data: EventToPayloadMap["property_updated"] }) => {
pageIds.forEach((pageId) => {
const pageInstance = getPageById(pageId);
const { name: updatedName, ...rest } = data;
@ -132,7 +132,7 @@ export const useRealtimePageEvents = ({
});
},
error: ({ pageIds, data }) => {
error: ({ pageIds, data }: { pageIds: string[]; data: EventToPayloadMap["error"] }) => {
const errorType = data.error_type;
const errorMessage = data.error_message || "An error occurred";
const errorCode = data.error_code;
@ -164,8 +164,9 @@ export const useRealtimePageEvents = ({
},
...customRealtimeEventHandlers,
};
});
}),
[getPageById, removePage, page, currentUser, getUserDisplayText, router, handlers, customRealtimeEventHandlers]
);
// The main function that will be returned from this hook
const updatePageProperties = useCallback(
@ -181,7 +182,7 @@ export const useRealtimePageEvents = ({
if (normalizedPageIds.length === 0) return;
// Get the handler for this message type
const handler = ACTION_HANDLERS[actionType];
const handler = ACTION_HANDLERS[actionType] as PageUpdateHandler<T> | undefined;
if (handler) {
// Now TypeScript knows that handler and data match in type

View file

@ -14,7 +14,8 @@
"fix:format": "turbo run fix:format",
"check": "turbo run check",
"check:lint": "turbo run check:lint",
"check:format": "turbo run check:format"
"check:format": "turbo run check:format",
"check:types": "turbo run check:types"
},
"devDependencies": {
"@prettier/plugin-oxc": "0.0.4",
@ -50,4 +51,4 @@
"engines": {
"node": ">=22.18.0"
}
}
}

View file

@ -45,7 +45,10 @@ const getPositionClassNames = (position: DialogPosition) =>
"top-8 left-1/2 -translate-x-1/2": position === "top",
});
const DialogPortal = memo(function DialogPortal({ children, ...props }) {
const DialogPortal = memo(function DialogPortal({
children,
...props
}: React.ComponentProps<typeof BaseDialog.Portal>) {
return (
<BaseDialog.Portal data-slot="dialog-portal" {...props}>
{children}
@ -54,12 +57,15 @@ const DialogPortal = memo(function DialogPortal({ children, ...props }) {
});
DialogPortal.displayName = "DialogPortal";
const DialogOverlay = memo(function DialogOverlay({ className, ...props }) {
const DialogOverlay = memo(function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof BaseDialog.Backdrop>) {
return <BaseDialog.Backdrop data-slot="dialog-overlay" className={cn(OVERLAY_CLASSNAME, className)} {...props} />;
});
DialogOverlay.displayName = "DialogOverlay";
const DialogComponent = memo(function DialogComponent({ children, ...props }) {
const DialogComponent = memo(function DialogComponent({ children, ...props }: DialogProps) {
return (
<BaseDialog.Root data-slot="dialog" {...props}>
{children}
@ -68,7 +74,10 @@ const DialogComponent = memo(function DialogComponent({ children, ...props }) {
});
DialogComponent.displayName = "Dialog";
const DialogTrigger = memo(function DialogTrigger({ children, ...props }) {
const DialogTrigger = memo(function DialogTrigger({
children,
...props
}: React.ComponentProps<typeof BaseDialog.Trigger>) {
return (
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
{children}
@ -100,7 +109,7 @@ const DialogPanel = forwardRef(function DialogPanel(
});
DialogPanel.displayName = "DialogPanel";
const DialogTitle = memo(function DialogTitle({ className, children, ...props }) {
const DialogTitle = memo(function DialogTitle({ className, children, ...props }: DialogTitleProps) {
return (
<BaseDialog.Title
data-slot="dialog-title"

View file

@ -1,4 +1,4 @@
import * as React from "react";
import { memo, useMemo } from "react";
import { Popover as BasePopover } from "@base-ui-components/react/popover";
import type { TPlacement, TSide, TAlign } from "../utils/placement";
import { convertPlacementToSideAndAlign } from "../utils/placement";
@ -13,7 +13,7 @@ export interface PopoverContentProps extends React.ComponentProps<typeof BasePop
}
// PopoverContent component
const PopoverContent = React.memo(function PopoverContent({
const PopoverContent = memo(function PopoverContent({
children,
className,
placement,
@ -23,9 +23,9 @@ const PopoverContent = React.memo(function PopoverContent({
containerRef,
positionerClassName,
...props
}) {
}: PopoverContentProps) {
// side and align calculations
const { finalSide, finalAlign } = React.useMemo(() => {
const { finalSide, finalAlign } = useMemo(() => {
if (placement) {
const converted = convertPlacementToSideAndAlign(placement);
return { finalSide: converted.side, finalAlign: converted.align };
@ -45,21 +45,21 @@ const PopoverContent = React.memo(function PopoverContent({
});
// wrapper components
const PopoverTrigger = React.memo(function PopoverTrigger(props) {
const PopoverTrigger = memo(function PopoverTrigger(props: React.ComponentProps<typeof BasePopover.Trigger>) {
return <BasePopover.Trigger data-slot="popover-trigger" {...props} />;
});
const PopoverPortal = React.memo(function PopoverPortal(props) {
const PopoverPortal = memo(function PopoverPortal(props: React.ComponentProps<typeof BasePopover.Portal>) {
return <BasePopover.Portal data-slot="popover-portal" {...props} />;
});
const PopoverPositioner = React.memo(function PopoverPositioner(props) {
const PopoverPositioner = memo(function PopoverPositioner(props: React.ComponentProps<typeof BasePopover.Positioner>) {
return <BasePopover.Positioner data-slot="popover-positioner" {...props} />;
});
// compound components
const Popover = Object.assign(
React.memo<React.ComponentProps<typeof BasePopover.Root>>(function Popover(props) {
memo(function Popover(props: React.ComponentProps<typeof BasePopover.Root>) {
return <BasePopover.Root data-slot="popover" {...props} />;
}),
{

View file

@ -37,4 +37,4 @@ const Skeleton = Object.assign(SkeletonRoot, { Item: SkeletonItem });
SkeletonRoot.displayName = "plane-ui-skeleton";
SkeletonItem.displayName = "plane-ui-skeleton-item";
export { Skeleton };
export { Skeleton, SkeletonRoot, SkeletonItem };

View file

@ -26,6 +26,7 @@
"@plane/eslint-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@prettier/plugin-oxc": "0.0.4",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"tsdown": "catalog:",

View file

@ -29,7 +29,7 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
function NavigationButton() {
return (
<Tooltip tooltipContent={selectedItem.title} position="bottom" disabled={isOpen}>
<Tooltip tooltipContent={selectedItem?.title} position="bottom" disabled={isOpen}>
<button
onClick={(e) => {
if (!isLast) {
@ -48,7 +48,7 @@ export function BreadcrumbNavigationDropdown(props: TBreadcrumbNavigationDropdow
<div className="flex @4xl:hidden text-custom-text-300">...</div>
<div className="hidden @4xl:flex gap-2">
{selectedItemIcon && <Breadcrumbs.Icon>{selectedItemIcon}</Breadcrumbs.Icon>}
<Breadcrumbs.Label>{selectedItem.title}</Breadcrumbs.Label>
<Breadcrumbs.Label>{selectedItem?.title}</Breadcrumbs.Label>
</div>
</button>
</Tooltip>

3
pnpm-lock.yaml generated
View file

@ -1330,6 +1330,9 @@ importers:
'@prettier/plugin-oxc':
specifier: 0.0.4
version: 0.0.4
'@types/node':
specifier: 'catalog:'
version: 22.12.0
'@types/react':
specifier: 'catalog:'
version: 18.3.11