From a49e18d0e5b311bf258e0715324ee628af051b4d Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Wed, 22 Apr 2026 13:39:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=A0=D0=A5=20-=20=D0=9C=D0=95=D0=96?= =?UTF-8?q?=D0=9F=D0=A0=D0=9E=D0=95=D0=9A=D0=A2=D0=9D=D0=90=D0=AF=20=D0=9A?= =?UTF-8?q?=D0=9E=D0=9C=D0=9C=D0=A3=D0=9D=D0=98=D0=9A=D0=90=D0=A6=D0=98?= =?UTF-8?q?=D0=AF:=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=D1=82=D1=8B=D1=85=20CustomSelect-?= =?UTF-8?q?=D0=BF=D0=B8=D0=BA=D0=B5=D1=80=D0=BE=D0=B2=20=D0=BD=D0=B0=20Sel?= =?UTF-8?q?ectionDropdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../external-contours/issue-header.tsx | 28 +++--- .../analytics/select/select-x-axis.tsx | 36 +++++--- .../analytics/select/select-y-axis.tsx | 31 +++---- .../components/common/selection-dropdown.tsx | 4 +- .../core/filters/date-filter-select.tsx | 23 ++--- .../components/core/theme/theme-switch.tsx | 92 ++++++++----------- .../dropdowns/estimate-type-dropdown.tsx | 25 +++-- .../issues/peek-overview/header.tsx | 27 +++--- .../modules/module-status-dropdown.tsx | 25 +++-- .../core/components/modules/select/status.tsx | 24 ++--- .../modules/sidebar-select/select-status.tsx | 25 +++-- .../profile/start-of-week-preference.tsx | 28 +++--- 12 files changed, 177 insertions(+), 191 deletions(-) diff --git a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx index 6fc9bd4..5cf8c7a 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx @@ -23,8 +23,9 @@ import { } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { TExternalContourRequest, TNameDescriptionLoader } from "@plane/types"; -import { ControlLink, CustomSelect, Header, Row, Tooltip } from "@plane/ui"; +import { ControlLink, Header, Row, Tooltip } from "@plane/ui"; import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; import { NameDescriptionUpdateStatus } from "@/components/issues/issue-update-status"; import { useProject } from "@/hooks/store/use-project"; import { useProjectExternalContoursBoard } from "@/hooks/store/use-project-external-contours-board"; @@ -228,19 +229,20 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont )} {currentMode && !embedIssue && ( - setPeekMode(value)} - customButton={ + - + } - > - {PEEK_OPTIONS.map((mode) => ( - + menuButtonWrapperClassName="flex items-center" + options={PEEK_OPTIONS.map((mode) => ({ + key: mode.key, + isChecked: currentMode.key === mode.key, + onClick: () => setPeekMode(mode.key), + title: (
{t(mode.i18n_title)}
-
- ))} -
+ ), + }))} + /> )} {issue?.project_id && issue.sequence_id && ( diff --git a/plane-src/apps/web/core/components/analytics/select/select-x-axis.tsx b/plane-src/apps/web/core/components/analytics/select/select-x-axis.tsx index 6effcfa..f0e1ec7 100644 --- a/plane-src/apps/web/core/components/analytics/select/select-x-axis.tsx +++ b/plane-src/apps/web/core/components/analytics/select/select-x-axis.tsx @@ -6,7 +6,7 @@ // plane package imports import type { ChartXAxisProperty } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; type Props = { value?: ChartXAxisProperty; @@ -21,16 +21,28 @@ type Props = { export function SelectXAxis(props: Props) { const { value, onChange, options, hiddenOptions, allowNoValue, label } = props; return ( - - {allowNoValue && No value} - {options.map((item) => { - if (hiddenOptions?.includes(item.value)) return null; - return ( - - {item.label} - - ); - })} - + onChange(null), + }, + ] + : []), + ...options + .filter((item) => !hiddenOptions?.includes(item.value)) + .map((item) => ({ + key: item.value, + title: item.label, + isChecked: value === item.value, + onClick: () => onChange(item.value), + })), + ]} + /> ); } diff --git a/plane-src/apps/web/core/components/analytics/select/select-y-axis.tsx b/plane-src/apps/web/core/components/analytics/select/select-y-axis.tsx index 64c69fb..5327b43 100644 --- a/plane-src/apps/web/core/components/analytics/select/select-y-axis.tsx +++ b/plane-src/apps/web/core/components/analytics/select/select-y-axis.tsx @@ -10,7 +10,7 @@ import { EEstimateSystem } from "@plane/constants"; import { ProjectIcon } from "@plane/propel/icons"; import type { ChartYAxisMetric } from "@plane/types"; // plane package imports -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; // hooks import { useProjectEstimates } from "@/hooks/store/estimates"; // plane web constants @@ -44,27 +44,22 @@ export const SelectYAxis = observer(function SelectYAxis({ value, onChange, hidd }; return ( - {options.find((v) => v.value === value)?.label ?? "Add Metric"} } - onChange={onChange} - maxHeight="lg" - > - {options.map((item) => { - if (hiddenOptions?.includes(item.value)) return null; - return ( - isEstimateEnabled(item.value) && ( - - {item.label} - - ) - ); - })} - + options={options + .filter((item) => !hiddenOptions?.includes(item.value)) + .filter((item) => isEstimateEnabled(item.value)) + .map((item) => ({ + key: item.value, + title: item.label, + isChecked: value === item.value, + onClick: () => onChange(item.value), + }))} + /> ); }); diff --git a/plane-src/apps/web/core/components/common/selection-dropdown.tsx b/plane-src/apps/web/core/components/common/selection-dropdown.tsx index acecfbc..3e3014c 100644 --- a/plane-src/apps/web/core/components/common/selection-dropdown.tsx +++ b/plane-src/apps/web/core/components/common/selection-dropdown.tsx @@ -25,11 +25,12 @@ type Props = { menuButtonWrapperClassName?: string; options: TSelectionDropdownOption[]; placement?: Placement; + tabIndex?: number; title?: ReactNode; }; export function SelectionDropdown(props: Props) { - const { disabled = false, menuButton, menuButtonWrapperClassName, options, placement = "bottom-start", title } = props; + const { disabled = false, menuButton, menuButtonWrapperClassName, options, placement = "bottom-start", tabIndex, title } = props; const renderedOptions = options.filter((option) => option.shouldRender !== false); @@ -39,6 +40,7 @@ export function SelectionDropdown(props: Props) { menuButtonWrapperClassName={menuButtonWrapperClassName} placement={placement} disabled={disabled} + tabIndex={tabIndex} > {({ closeDropdown }) => (
diff --git a/plane-src/apps/web/core/components/core/filters/date-filter-select.tsx b/plane-src/apps/web/core/components/core/filters/date-filter-select.tsx index fd106a9..f708c41 100644 --- a/plane-src/apps/web/core/components/core/filters/date-filter-select.tsx +++ b/plane-src/apps/web/core/components/core/filters/date-filter-select.tsx @@ -8,7 +8,7 @@ import React from "react"; import { CalendarDays } from "lucide-react"; // ui import { CalendarAfterIcon, CalendarBeforeIcon } from "@plane/propel/icons"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; type Props = { title: string; @@ -42,9 +42,8 @@ const dueDateRange: DueDate[] = [ export function DateFilterSelect({ title, value, onChange }: Props) { return ( - {dueDateRange.find((item) => item.value === value)?.icon} @@ -52,16 +51,18 @@ export function DateFilterSelect({ title, value, onChange }: Props) {
} - onChange={onChange} - > - {dueDateRange.map((option, index) => ( - + menuButtonWrapperClassName="flex items-center" + options={dueDateRange.map((option) => ({ + key: option.value, + isChecked: value === option.value, + onClick: () => onChange(option.value), + title: (
{option.icon} {title} {option.name}
-
- ))} - + ), + }))} + /> ); } diff --git a/plane-src/apps/web/core/components/core/theme/theme-switch.tsx b/plane-src/apps/web/core/components/core/theme/theme-switch.tsx index b9de9bc..fc34351 100644 --- a/plane-src/apps/web/core/components/core/theme/theme-switch.tsx +++ b/plane-src/apps/web/core/components/core/theme/theme-switch.tsx @@ -9,7 +9,7 @@ import type { I_THEME_OPTION } from "@plane/constants"; import { THEME_OPTIONS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // constants -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; // ui type Props = { @@ -22,70 +22,54 @@ export function ThemeSwitch(props: Props) { // translation const { t } = useTranslation(); + const renderThemeSwatch = (themeOption: I_THEME_OPTION) => ( +
+
+
+
+ ); + return ( - -
-
-
-
+ {renderThemeSwatch(value)} {t(value.key)}
) : ( t("select_your_theme") ) } - onChange={onChange} - buttonClassName="border border-subtle-1" - placement="bottom-end" - input - > - {THEME_OPTIONS.map((themeOption) => ( - + menuButtonWrapperClassName="flex w-full items-center justify-between rounded-full border border-subtle-1 px-3 py-2 text-13" + options={THEME_OPTIONS.map((themeOption) => ({ + key: themeOption.value, + isChecked: value?.value === themeOption.value, + onClick: () => onChange(themeOption), + title: (
-
-
-
-
+ {renderThemeSwatch(themeOption)} {t(themeOption.key)}
- - ))} - + ), + }))} + /> ); } diff --git a/plane-src/apps/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx b/plane-src/apps/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx index 5104570..1d1cdda 100644 --- a/plane-src/apps/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx +++ b/plane-src/apps/web/core/components/cycles/dropdowns/estimate-type-dropdown.tsx @@ -8,7 +8,7 @@ import React from "react"; import { observer } from "mobx-react"; import type { TCycleEstimateType } from "@plane/types"; import { EEstimateSystem } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; import { useProjectEstimates } from "@/hooks/store/estimates"; import { useCycle } from "@/hooks/store/use-cycle"; // local imports @@ -30,19 +30,16 @@ export const EstimateTypeDropdown = observer(function EstimateTypeDropdown(props return (getIsPointsDataAvailable(cycleId) || isCurrentProjectEstimateEnabled) && currentProjectEstimateType !== EEstimateSystem.CATEGORIES ? (
- {cycleEstimateOptions.find((v) => v.value === value)?.label ?? "None"}} - onChange={onChange} - maxHeight="lg" - buttonClassName="bg-surface-2 border-none rounded-sm text-13 font-medium " - > - {cycleEstimateOptions.map((item) => ( - - {item.label} - - ))} - + {cycleEstimateOptions.find((v) => v.value === value)?.label ?? "None"}} + menuButtonWrapperClassName="flex items-center rounded-sm bg-surface-2 px-2 py-1 text-13 font-medium" + options={cycleEstimateOptions.map((item) => ({ + key: item.value, + title: item.label, + isChecked: value === item.value, + onClick: () => onChange(item.value), + }))} + />
) : showDefault ? ( {cycleEstimateOptions.find((v) => v.value === value)?.label ?? value} diff --git a/plane-src/apps/web/core/components/issues/peek-overview/header.tsx b/plane-src/apps/web/core/components/issues/peek-overview/header.tsx index a4c6fd6..1176b1f 100644 --- a/plane-src/apps/web/core/components/issues/peek-overview/header.tsx +++ b/plane-src/apps/web/core/components/issues/peek-overview/header.tsx @@ -15,7 +15,7 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { Tooltip } from "@plane/propel/tooltip"; import type { TNameDescriptionLoader } from "@plane/types"; import { EIssuesStoreType } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; import { copyUrlToClipboard, generateWorkItemLink } from "@plane/utils"; // hooks import { useIssueDetail } from "@/hooks/store/use-issue-detail"; @@ -185,19 +185,20 @@ export const IssuePeekOverviewHeader = observer(function IssuePeekOverviewHeader {currentMode && showLayoutSwitcher && (
- setPeekMode(val)} - customButton={ + - + } - > - {PEEK_OPTIONS.map((mode) => ( - + menuButtonWrapperClassName="flex items-center" + options={PEEK_OPTIONS.map((mode) => ({ + key: mode.key, + isChecked: currentMode.key === mode.key, + onClick: () => setPeekMode(mode.key), + title: (
{t(mode.i18n_title)}
-
- ))} -
+ ), + }))} + />
)} {metaSlot} diff --git a/plane-src/apps/web/core/components/modules/module-status-dropdown.tsx b/plane-src/apps/web/core/components/modules/module-status-dropdown.tsx index ef532e6..65886ab 100644 --- a/plane-src/apps/web/core/components/modules/module-status-dropdown.tsx +++ b/plane-src/apps/web/core/components/modules/module-status-dropdown.tsx @@ -11,7 +11,7 @@ import { useTranslation } from "@plane/i18n"; import type { TModuleStatus } from "@plane/propel/icons"; import { ModuleStatusIcon } from "@plane/propel/icons"; import type { IModule } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; type Props = { isDisabled: boolean; @@ -27,8 +27,8 @@ export const ModuleStatusDropdown = observer(function ModuleStatusDropdown(props if (!moduleStatus) return <>; return ( - } - value={moduleStatus?.value} - onChange={(val: TModuleStatus) => { - handleModuleDetailsChange({ status: val }); - }} disabled={isDisabled} - > - {MODULE_STATUS.map((status) => ( - + menuButtonWrapperClassName="flex" + options={MODULE_STATUS.map((status) => ({ + key: status.value, + isChecked: moduleStatus?.value === status.value, + onClick: () => handleModuleDetailsChange({ status: status.value as TModuleStatus }), + title: (
{t(status.i18n_label)}
-
- ))} -
+ ), + }))} + /> ); }); diff --git a/plane-src/apps/web/core/components/modules/select/status.tsx b/plane-src/apps/web/core/components/modules/select/status.tsx index e62b59a..c85a18a 100644 --- a/plane-src/apps/web/core/components/modules/select/status.tsx +++ b/plane-src/apps/web/core/components/modules/select/status.tsx @@ -14,7 +14,7 @@ import { useTranslation } from "@plane/i18n"; import { StatePropertyIcon, ModuleStatusIcon } from "@plane/propel/icons"; import type { IModule } from "@plane/types"; // ui -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; // types // constants @@ -34,9 +34,8 @@ export function ModuleStatusSelect({ control, error, tabIndex }: Props) { render={({ field: { value, onChange } }) => { const selectedValue = MODULE_STATUS.find((s) => s.value === value); return ( - @@ -50,19 +49,20 @@ export function ModuleStatusSelect({ control, error, tabIndex }: Props) { )}
} - onChange={onChange} tabIndex={tabIndex} - noChevron - > - {MODULE_STATUS.map((status) => ( - + menuButtonWrapperClassName="flex" + options={MODULE_STATUS.map((status) => ({ + key: status.value, + isChecked: value === status.value, + onClick: () => onChange(status.value), + title: (
{t(status.i18n_label)}
-
- ))} - + ), + }))} + /> ); }} /> diff --git a/plane-src/apps/web/core/components/modules/sidebar-select/select-status.tsx b/plane-src/apps/web/core/components/modules/sidebar-select/select-status.tsx index 541e315..fb72cf6 100644 --- a/plane-src/apps/web/core/components/modules/sidebar-select/select-status.tsx +++ b/plane-src/apps/web/core/components/modules/sidebar-select/select-status.tsx @@ -14,7 +14,7 @@ import { useTranslation } from "@plane/i18n"; import { StatePropertyIcon } from "@plane/propel/icons"; import type { IModule } from "@plane/types"; // ui -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; // types // common // constants @@ -38,8 +38,8 @@ export function SidebarStatusSelect({ control, submitChanges, watch }: Props) { control={control} name="status" render={({ field: { value } }) => ( - } - value={value} - onChange={(value: any) => { - submitChanges({ status: value }); - }} - > - {MODULE_STATUS.map((option) => ( - + menuButtonWrapperClassName="flex" + options={MODULE_STATUS.map((option) => ({ + key: option.value, + isChecked: value === option.value, + onClick: () => submitChanges({ status: option.value }), + title: (
{t(option.i18n_label)}
-
- ))} -
+ ), + }))} + /> )} />
diff --git a/plane-src/apps/web/core/components/profile/start-of-week-preference.tsx b/plane-src/apps/web/core/components/profile/start-of-week-preference.tsx index 93893b4..dfae795 100644 --- a/plane-src/apps/web/core/components/profile/start-of-week-preference.tsx +++ b/plane-src/apps/web/core/components/profile/start-of-week-preference.tsx @@ -9,7 +9,7 @@ import { observer } from "mobx-react"; import { START_OF_THE_WEEK_OPTIONS } from "@plane/constants"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { EStartOfTheWeek } from "@plane/types"; -import { CustomSelect } from "@plane/ui"; +import { SelectionDropdown } from "@/components/common/selection-dropdown"; // components import { SettingsControlItem } from "@/components/settings/control-item"; // hooks @@ -38,23 +38,17 @@ export const StartOfWeekPreference = observer(function StartOfWeekPreference(pro title={props.option.title} description={props.option.description} control={ - - <> - {START_OF_THE_WEEK_OPTIONS.map((day) => ( - - {day.label} - - ))} - - + menuButton={getStartOfWeekLabel(userProfile.start_of_the_week)} + menuButtonWrapperClassName="flex w-full items-center justify-between rounded-full border border-subtle-1 px-3 py-2 text-13" + options={START_OF_THE_WEEK_OPTIONS.map((day) => ({ + key: `${day.value}`, + title: day.label, + isChecked: userProfile.start_of_the_week === day.value, + onClick: () => handleStartOfWeekChange(day.value), + }))} + /> } /> );