refactor: migrate ToggleSwitch to @makeplane/propel (#9696)

* refactor: migrate web ToggleSwitch to @makeplane/propel Switch

Replace @plane/ui ToggleSwitch with @makeplane/propel Switch in apps/web.
Wire the package on web and import Propel styles so Switch utilities emit.

* style: fix comboButton indentation in dropdowns

* chore: use workspace catalog for @makeplane/propel
This commit is contained in:
Anmol Singh Bhatia
2026-08-28 00:10:53 +05:30
committed by GitHub
parent c3cc04777a
commit ad57941526
18 changed files with 161 additions and 96 deletions

View File

@@ -14,7 +14,8 @@ import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IApiToken } from "@plane/types";
// ui
import { CustomSelect, Input, TextArea, ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { CustomSelect, Input, TextArea } from "@plane/ui";
import { cn, renderFormattedDate, renderFormattedTime } from "@plane/utils";
// components
import { DateDropdown } from "@/components/dropdowns/date";
@@ -240,7 +241,12 @@ export function CreateApiTokenForm(props: Props) {
<div className="flex items-center justify-between gap-2 border-t-[0.5px] border-subtle px-5 py-4">
<div className="flex cursor-pointer items-center gap-1.5" onClick={toggleNeverExpires}>
<div className="flex cursor-pointer items-center justify-center">
<ToggleSwitch value={neverExpires} onChange={() => {}} size="sm" />
<Switch
size="sm"
checked={neverExpires}
onCheckedChange={() => {}}
aria-label={t("workspace_settings.settings.api_tokens.never_expires")}
/>
</div>
<span className="text-11">{t("workspace_settings.settings.api_tokens.never_expires")}</span>
</div>

View File

@@ -12,7 +12,8 @@ import { ArchiveRestore } from "lucide-react";
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import type { IProject } from "@plane/types";
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { CustomSelect, Loader } from "@plane/ui";
// component
import { SelectMonthModal } from "@/components/automation";
import { SettingsControlItem } from "@/components/settings/control-item";
@@ -76,7 +77,13 @@ export const AutoArchiveAutomation = observer(function AutoArchiveAutomation(pro
title={t("project_settings.automations.auto-archive.title")}
description={t("project_settings.automations.auto-archive.description")}
control={
<ToggleSwitch value={autoArchiveStatus} onChange={handleToggleArchive} size="sm" disabled={!isAdmin} />
<Switch
size="sm"
checked={autoArchiveStatus}
onCheckedChange={handleToggleArchive}
disabled={!isAdmin}
aria-label={t("project_settings.automations.auto-archive.title")}
/>
}
/>
</div>

View File

@@ -13,7 +13,8 @@ import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel, EIc
import { useTranslation } from "@plane/i18n";
import { StateGroupIcon, StatePropertyIcon } from "@plane/propel/icons";
import type { IProject } from "@plane/types";
import { CustomSelect, CustomSearchSelect, ToggleSwitch, Loader } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { CustomSelect, CustomSearchSelect, Loader } from "@plane/ui";
import { SelectMonthModal } from "@/components/automation";
import { SettingsControlItem } from "@/components/settings/control-item";
// hooks
@@ -94,17 +95,18 @@ export const AutoCloseAutomation = observer(function AutoCloseAutomation(props:
title={t("project_settings.automations.auto-close.title")}
description={t("project_settings.automations.auto-close.description")}
control={
<ToggleSwitch
value={autoCloseStatus}
onChange={() => {
<Switch
size="sm"
checked={autoCloseStatus}
onCheckedChange={() => {
if (currentProjectDetails?.close_in === 0) {
void handleChange({ close_in: 1, default_state: defaultState });
} else {
void handleChange({ close_in: 0, default_state: null });
}
}}
size="sm"
disabled={!isAdmin}
aria-label={t("project_settings.automations.auto-close.title")}
/>
}
/>

View File

@@ -16,7 +16,8 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Tooltip } from "@plane/propel/tooltip";
import type { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types";
// ui
import { Loader, ToggleSwitch, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { Loader, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
import { generateWorkItemLink, getTabIndex } from "@plane/utils";
// helpers
// hooks
@@ -198,7 +199,12 @@ export function ExistingIssuesListModal(props: Props) {
isWorkspaceLevel ? "text-primary" : "text-secondary"
}`}
>
<ToggleSwitch value={isWorkspaceLevel} onChange={() => setIsWorkspaceLevel((prevData) => !prevData)} />
<Switch
size="sm"
checked={isWorkspaceLevel}
onCheckedChange={setIsWorkspaceLevel}
aria-label={t("common.workspace_level")}
/>
<button
type="button"
onClick={() => setIsWorkspaceLevel((prevData) => !prevData)}

View File

@@ -7,7 +7,7 @@
import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// hooks
import { useProjectEstimates } from "@/hooks/store/estimates";
import { useProject } from "@/hooks/store/use-project";
@@ -54,11 +54,14 @@ export const EstimateDisableSwitch = observer(function EstimateDisableSwitch(pro
};
return (
<ToggleSwitch
value={Boolean(currentProjectActiveEstimate)}
onChange={disableEstimate}
disabled={!isAdmin}
<Switch
size="sm"
checked={Boolean(currentProjectActiveEstimate)}
onCheckedChange={() => {
void disableEstimate();
}}
disabled={!isAdmin}
aria-label="Toggle estimates"
/>
);
});

View File

@@ -22,7 +22,8 @@ import { createRoot } from "react-dom/client";
import { useTranslation } from "@plane/i18n";
import type { InstructionType } from "@plane/types";
// plane ui
import { DropIndicator, ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { DropIndicator } from "@plane/ui";
// plane utils
import { cn } from "@plane/utils";
// hooks
@@ -136,9 +137,11 @@ export const WidgetItem = observer(function WidgetItem(props: Props) {
<WidgetItemDragHandle sort_order={widget.sort_order} isDragging={isDragging} />
<div>{t(widgetTitle, { count: 1 })}</div>
</div>
<ToggleSwitch
value={widget.is_enabled}
onChange={() => handleToggle(workspaceSlug.toString(), widget.key, !widget.is_enabled)}
<Switch
size="sm"
checked={widget.is_enabled}
onCheckedChange={(enabled) => handleToggle(workspaceSlug.toString(), widget.key, enabled)}
aria-label={t(widgetTitle, { count: 1 })}
/>
</div>
{isLastChild && <DropIndicator isVisible={instruction === "reorder-below"} />}

View File

@@ -16,7 +16,7 @@ import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TIssue } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { renderFormattedPayloadDate, getTabIndex } from "@plane/utils";
// hooks
import { useProjectInbox } from "@/hooks/store/use-project-inbox";
@@ -212,7 +212,7 @@ export const InboxIssueCreateRoot = observer(function InboxIssueCreateRoot(props
role="button"
tabIndex={getIndex("create_more")}
>
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
<Switch size="sm" checked={createMore} onCheckedChange={() => {}} aria-label={t("create_more")} />
<span className="text-11">{t("create_more")}</span>
</div>
<div className="flex items-center gap-3">

View File

@@ -18,7 +18,7 @@ import { EIssueFilterType } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CheckIcon, ChevronUpIcon } from "@plane/propel/icons";
import type { TCalendarLayouts, TSupportedFilterForUpdate } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// types
// constants
import { CALENDAR_LAYOUTS } from "@plane/constants";
@@ -154,11 +154,13 @@ export const CalendarOptionsDropdown = observer(function CalendarOptionsDropdown
onClick={handleToggleWeekends}
>
{t("common.actions.show_weekends")}
<ToggleSwitch
value={showWeekends}
onChange={() => {
if (windowWidth <= 768) closePopover(); // close the popover on mobile
<Switch
size="sm"
checked={showWeekends}
onCheckedChange={() => {
if (windowWidth <= 768) closePopover();
}}
aria-label={t("common.actions.show_weekends")}
/>
</button>
</div>

View File

@@ -20,7 +20,7 @@ import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TIssue, TWorkspaceDraftIssue } from "@plane/types";
// hooks
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import {
convertWorkItemDataToSearchResponse,
getUpdateFormDataForReset,
@@ -454,7 +454,12 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
}}
role="button"
>
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
<Switch
size="sm"
checked={isCreateMoreToggleEnabled}
onCheckedChange={() => {}}
aria-label={t("create_more")}
/>
<span className="text-caption-sm-regular">{t("create_more")}</span>
</div>
)}

View File

@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
import { ArrowUpToLine, Clipboard, History } from "lucide-react";
// plane imports
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// hooks
import { useAppRouter } from "@/hooks/use-app-router";
import { usePageFilters } from "@/hooks/use-page-filters";
@@ -55,7 +55,7 @@ export const PageOptionsDropdown = observer(function PageOptionsDropdown(props:
customContent: (
<>
Full width
<ToggleSwitch value={isFullWidth} onChange={() => {}} />
<Switch size="sm" checked={isFullWidth} onCheckedChange={() => {}} aria-label="Full width" />
</>
),
className: "flex items-center justify-between gap-2",
@@ -66,7 +66,12 @@ export const PageOptionsDropdown = observer(function PageOptionsDropdown(props:
customContent: (
<>
Sticky toolbar
<ToggleSwitch value={isStickyToolbarEnabled} onChange={() => {}} />
<Switch
size="sm"
checked={isStickyToolbarEnabled}
onCheckedChange={() => {}}
aria-label="Sticky toolbar"
/>
</>
),
className: "flex items-center justify-between gap-2",

View File

@@ -7,7 +7,7 @@
import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
type Props = {
isWorkspaceLevel: boolean;
@@ -25,11 +25,12 @@ export const PowerKModalFooter = observer(function PowerKModalFooter(props: Prop
<div />
<div className="flex items-center gap-2">
<span className="text-11 text-tertiary">{t("power_k.footer.workspace_level")}</span>
<ToggleSwitch
value={isWorkspaceLevel}
onChange={() => onWorkspaceLevelChange(!isWorkspaceLevel)}
disabled={!projectId}
<Switch
size="sm"
checked={isWorkspaceLevel}
onCheckedChange={onWorkspaceLevelChange}
disabled={!projectId}
aria-label={t("power_k.footer.workspace_level")}
/>
</div>
</div>

View File

@@ -14,7 +14,8 @@ import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IProject, IUserLite, IWorkspace } from "@plane/types";
import { Loader, ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { Loader } from "@plane/ui";
// constants
import { PROJECT_DETAILS } from "@plane/constants";
// hooks
@@ -187,11 +188,12 @@ export const ProjectSettingsMemberDefaults = observer(function ProjectSettingsMe
description="This will allow guests to have view access to all the project work items."
>
<div className="flex items-center justify-end">
<ToggleSwitch
value={!!currentProjectDetails?.guest_view_all_features}
onChange={() => toggleGuestViewAllIssues(!currentProjectDetails?.guest_view_all_features)}
disabled={!isAdmin}
<Switch
size="sm"
checked={!!currentProjectDetails?.guest_view_all_features}
onCheckedChange={toggleGuestViewAllIssues}
disabled={!isAdmin}
aria-label="Guest access"
/>
</div>
</DefaultSettingItem>

View File

@@ -16,7 +16,8 @@ import { GlobeIcon, NewTabIcon, CheckIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { TProjectPublishLayouts, TProjectPublishSettings } from "@plane/types";
// ui
import { Loader, ToggleSwitch, CustomSelect, ModalCore, EModalWidth } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { Loader, CustomSelect, ModalCore, EModalWidth } from "@plane/ui";
// helpers
import { copyTextToClipboard } from "@plane/utils";
// hooks
@@ -283,7 +284,7 @@ export const PublishProjectModal = observer(function PublishProjectModal(props:
control={control}
name="is_comments_enabled"
render={({ field: { onChange, value } }) => (
<ToggleSwitch value={!!value} onChange={onChange} size="sm" />
<Switch size="sm" checked={!!value} onCheckedChange={onChange} aria-label="Allow comments" />
)}
/>
</div>
@@ -293,7 +294,7 @@ export const PublishProjectModal = observer(function PublishProjectModal(props:
control={control}
name="is_reactions_enabled"
render={({ field: { onChange, value } }) => (
<ToggleSwitch value={!!value} onChange={onChange} size="sm" />
<Switch size="sm" checked={!!value} onCheckedChange={onChange} aria-label="Allow reactions" />
)}
/>
</div>
@@ -303,7 +304,7 @@ export const PublishProjectModal = observer(function PublishProjectModal(props:
control={control}
name="is_votes_enabled"
render={({ field: { onChange, value } }) => (
<ToggleSwitch value={!!value} onChange={onChange} size="sm" />
<Switch size="sm" checked={!!value} onCheckedChange={onChange} aria-label="Allow voting" />
)}
/>
</div>

View File

@@ -7,7 +7,7 @@
import Link from "next/link";
import { ChevronRightIcon } from "@plane/propel/icons";
import { EPillVariant, Pill, EPillSize } from "@plane/propel/pill";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
import { joinUrlPath } from "@plane/utils";
type Props = {
@@ -35,11 +35,12 @@ export function ProjectFeatureToggle(props: Props) {
</div>
</Link>
) : (
<ToggleSwitch
value={value}
onChange={() => handleSubmit(featureItem?.key, featureItem?.property)}
disabled={disabled}
<Switch
size="sm"
checked={value}
onCheckedChange={() => handleSubmit(featureItem?.key, featureItem?.property)}
disabled={disabled}
aria-label="Toggle project feature"
/>
);
}

View File

@@ -11,7 +11,7 @@ import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IUserEmailNotificationSettings } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// components
import { SettingsControlItem } from "@/components/settings/control-item";
// services
@@ -68,13 +68,14 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
control={control}
name="property_change"
render={({ field: { value, onChange } }) => (
<ToggleSwitch
value={value}
onChange={(newValue) => {
onChange(newValue);
handleSettingChange("property_change", newValue);
}}
<Switch
size="sm"
checked={value}
onCheckedChange={(newValue) => {
onChange(newValue);
void handleSettingChange("property_change", newValue);
}}
aria-label={t("property_changes")}
/>
)}
/>
@@ -88,13 +89,14 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
control={control}
name="state_change"
render={({ field: { value, onChange } }) => (
<ToggleSwitch
value={value}
onChange={(newValue) => {
onChange(newValue);
handleSettingChange("state_change", newValue);
}}
<Switch
size="sm"
checked={value}
onCheckedChange={(newValue) => {
onChange(newValue);
void handleSettingChange("state_change", newValue);
}}
aria-label={t("state_change")}
/>
)}
/>
@@ -109,13 +111,14 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
control={control}
name="issue_completed"
render={({ field: { value, onChange } }) => (
<ToggleSwitch
value={value}
onChange={(newValue) => {
onChange(newValue);
handleSettingChange("issue_completed", newValue);
}}
<Switch
size="sm"
checked={value}
onCheckedChange={(newValue) => {
onChange(newValue);
void handleSettingChange("issue_completed", newValue);
}}
aria-label={t("issue_completed")}
/>
)}
/>
@@ -130,13 +133,14 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
control={control}
name="comment"
render={({ field: { value, onChange } }) => (
<ToggleSwitch
value={value}
onChange={(newValue) => {
onChange(newValue);
handleSettingChange("comment", newValue);
}}
<Switch
size="sm"
checked={value}
onCheckedChange={(newValue) => {
onChange(newValue);
void handleSettingChange("comment", newValue);
}}
aria-label={t("comments")}
/>
)}
/>
@@ -150,13 +154,14 @@ export const NotificationsProfileSettingsForm = observer(function NotificationsP
control={control}
name="mention"
render={({ field: { value, onChange } }) => (
<ToggleSwitch
value={value}
onChange={(newValue) => {
onChange(newValue);
handleSettingChange("mention", newValue);
}}
<Switch
size="sm"
checked={value}
onCheckedChange={(newValue) => {
onChange(newValue);
void handleSettingChange("mention", newValue);
}}
aria-label={t("mentions")}
/>
)}
/>

View File

@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
// plane imports
import { setPromiseToast } from "@plane/propel/toast";
import type { IProject } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// components
import { SettingsBoxedControlItem } from "@/components/settings/boxed-control-item";
// hooks
@@ -60,7 +60,15 @@ export const ProjectSettingsFeatureControlItem = observer(function ProjectSettin
<SettingsBoxedControlItem
title={title}
description={description}
control={<ToggleSwitch value={value} onChange={handleSubmit} disabled={disabled} size="sm" />}
control={
<Switch
size="sm"
checked={value}
onCheckedChange={handleSubmit}
disabled={disabled}
aria-label={typeof title === "string" ? title : "Toggle project feature"}
/>
}
/>
);
});

View File

@@ -8,8 +8,7 @@ import type { Control } from "react-hook-form";
import { Controller } from "react-hook-form";
// constants
import type { IWebhook } from "@plane/types";
// ui
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
interface IWebHookToggle {
control: Control<IWebhook, any>;
@@ -23,13 +22,7 @@ export function WebhookToggle({ control }: IWebHookToggle) {
control={control}
name="is_active"
render={({ field: { onChange, value } }) => (
<ToggleSwitch
value={value}
onChange={(val: boolean) => {
onChange(val);
}}
size="sm"
/>
<Switch size="sm" checked={value} onCheckedChange={onChange} aria-label="Enable webhook" />
)}
/>
</div>

View File

@@ -8,7 +8,7 @@ import Link from "next/link";
import { useParams } from "next/navigation";
// Plane imports
import type { IWebhook } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { Switch } from "@makeplane/propel/components/switch";
// hooks
import { useWebhook } from "@/hooks/store/use-webhook";
@@ -35,8 +35,23 @@ export function WebhooksListItem(props: IWebhookListItem) {
className="flex items-center justify-between gap-4"
>
<h5 className="truncate text-body-sm-medium">{webhook.url}</h5>
<div className="shrink-0">
<ToggleSwitch value={webhook.is_active} onChange={handleToggle} />
<div
className="shrink-0"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onKeyDown={(e) => e.stopPropagation()}
role="presentation"
>
<Switch
size="sm"
checked={webhook.is_active}
onCheckedChange={() => {
void handleToggle();
}}
aria-label="Toggle webhook"
/>
</div>
</Link>
</div>