mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
1 Commits
fix/bubble
...
add-layout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32b4019a69 |
3
packages/types/src/views.d.ts
vendored
3
packages/types/src/views.d.ts
vendored
@@ -13,6 +13,9 @@ export interface IProjectView {
|
||||
is_favorite: boolean;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
isLocked: boolean | null;
|
||||
isPrivate: boolean | null;
|
||||
isPublished: boolean | null;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: IIssueFilterOptions;
|
||||
|
||||
@@ -31,7 +31,7 @@ export const DropdownButton: React.FC<IMultiSelectDropdownButton | ISingleSelect
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
{buttonContent ? <>{buttonContent(isOpen)}</> : <span className={cn("", buttonClassName)}>{value}</span>}
|
||||
{buttonContent ? <>{buttonContent(isOpen, value)}</> : <span className={cn("", buttonClassName)}>{value}</span>}
|
||||
</button>
|
||||
</Combobox.Button>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
||||
disableSearch,
|
||||
keyExtractor,
|
||||
options,
|
||||
handleClose,
|
||||
value,
|
||||
renderItem,
|
||||
loader,
|
||||
@@ -46,7 +47,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
||||
options?.map((option) => (
|
||||
<Combobox.Option
|
||||
key={keyExtractor(option)}
|
||||
value={option.data[option.value]}
|
||||
value={option.value}
|
||||
className={({ active, selected }) =>
|
||||
cn(
|
||||
"flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5",
|
||||
@@ -58,6 +59,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
||||
option.className && option.className({ active, selected })
|
||||
)
|
||||
}
|
||||
onClick={handleClose}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
@@ -65,7 +67,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
|
||||
<>{renderItem({ value: option.data[option.value], selected })}</>
|
||||
) : (
|
||||
<>
|
||||
<span className="flex-grow truncate">{value}</span>
|
||||
<span className="flex-grow truncate">{option.value}</span>
|
||||
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</>
|
||||
)}
|
||||
|
||||
8
packages/ui/src/dropdown/dropdown.d.ts
vendored
8
packages/ui/src/dropdown/dropdown.d.ts
vendored
@@ -24,8 +24,8 @@ export interface IDropdown {
|
||||
// options props
|
||||
keyExtractor: (option: TDropdownOption) => string;
|
||||
optionsContainerClassName?: string;
|
||||
queryArray: string[];
|
||||
sortByKey: string;
|
||||
queryArray?: string[];
|
||||
sortByKey?: string;
|
||||
firstItem?: (optionValue: string) => boolean;
|
||||
renderItem?: ({ value, selected }: { value: string; selected: boolean }) => React.ReactNode;
|
||||
loader?: React.ReactNode;
|
||||
@@ -52,7 +52,7 @@ export interface ISingleSelectDropdown extends IDropdown {
|
||||
|
||||
export interface IDropdownButton {
|
||||
isOpen: boolean;
|
||||
buttonContent?: (isOpen: boolean) => React.ReactNode;
|
||||
buttonContent?: (isOpen: boolean, value: string | string[] | undefined) => React.ReactNode;
|
||||
buttonClassName?: string;
|
||||
buttonContainerClassName?: string;
|
||||
handleOnClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
@@ -79,6 +79,8 @@ export interface IDropdownOptions {
|
||||
inputContainerClassName?: string;
|
||||
disableSearch?: boolean;
|
||||
|
||||
handleClose?: () => void;
|
||||
|
||||
keyExtractor: (option: TDropdownOption) => string;
|
||||
renderItem: (({ value, selected }: { value: string; selected: boolean }) => React.ReactNode) | undefined;
|
||||
options: TDropdownOption[] | undefined;
|
||||
|
||||
@@ -90,10 +90,12 @@ export const MultiSelectDropdown: FC<IMultiSelectDropdown> = (props) => {
|
||||
const sortedOptions = useMemo(() => {
|
||||
if (!options) return undefined;
|
||||
|
||||
const filteredOptions = (options || []).filter((options) => {
|
||||
const queryString = queryArray.map((query) => options.data[query]).join(" ");
|
||||
return queryString.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
const filteredOptions = queryArray
|
||||
? (options || []).filter((options) => {
|
||||
const queryString = queryArray.map((query) => options.data[query]).join(" ");
|
||||
return queryString.toLowerCase().includes(query.toLowerCase());
|
||||
})
|
||||
: options;
|
||||
|
||||
if (disableSorting) return filteredOptions;
|
||||
|
||||
|
||||
@@ -90,12 +90,14 @@ export const Dropdown: FC<ISingleSelectDropdown> = (props) => {
|
||||
const sortedOptions = useMemo(() => {
|
||||
if (!options) return undefined;
|
||||
|
||||
const filteredOptions = (options || []).filter((options) => {
|
||||
const queryString = queryArray.map((query) => options.data[query]).join(" ");
|
||||
return queryString.toLowerCase().includes(query.toLowerCase());
|
||||
});
|
||||
const filteredOptions = queryArray
|
||||
? (options || []).filter((options) => {
|
||||
const queryString = queryArray.map((query) => options.data[query]).join(" ");
|
||||
return queryString.toLowerCase().includes(query.toLowerCase());
|
||||
})
|
||||
: options;
|
||||
|
||||
if (disableSorting) return filteredOptions;
|
||||
if (disableSorting || !sortByKey) return filteredOptions;
|
||||
|
||||
return sortBy(filteredOptions, [
|
||||
(option) => firstItem && firstItem(option.data[option.value]),
|
||||
@@ -157,6 +159,7 @@ export const Dropdown: FC<ISingleSelectDropdown> = (props) => {
|
||||
value={value}
|
||||
renderItem={renderItem}
|
||||
loader={loader}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
</Combobox.Options>
|
||||
|
||||
26
web/core/components/dropdowns/layout.tsx
Normal file
26
web/core/components/dropdowns/layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { Dropdown } from "@plane/ui";
|
||||
import { EIssueLayoutTypes, ISSUE_LAYOUT_MAP } from "@/constants/issue";
|
||||
|
||||
type TLayoutDropDown = {
|
||||
onChange: (value: EIssueLayoutTypes) => void;
|
||||
value: EIssueLayoutTypes;
|
||||
};
|
||||
export const LayoutDropDown = observer((props: TLayoutDropDown) => {
|
||||
const { onChange, value = EIssueLayoutTypes.LIST } = props;
|
||||
|
||||
const options = Object.values(ISSUE_LAYOUT_MAP).map((issueLayout) => ({
|
||||
data: issueLayout.key,
|
||||
value: issueLayout.key,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
onChange={onChange as (value: string) => void}
|
||||
value={value?.toString()}
|
||||
keyExtractor={(option) => option.value}
|
||||
options={options}
|
||||
disableSearch
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -11,11 +11,12 @@ import { Button, EmojiIconPicker, EmojiIconPickerTypes, Input, PhotoFilterIcon,
|
||||
import { Logo } from "@/components/common";
|
||||
import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
import { EIssueLayoutTypes, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// helpers
|
||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||
import { LayoutDropDown } from "../dropdowns/layout";
|
||||
|
||||
type Props = {
|
||||
data?: IProjectView | null;
|
||||
@@ -48,7 +49,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
||||
reset,
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm<IProjectView>({
|
||||
} = useForm<IProjectView & { layout: EIssueLayoutTypes }>({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
@@ -90,13 +91,13 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateUpdateView = async (formData: IProjectView) => {
|
||||
const handleCreateUpdateView = async (formData: IProjectView & { layout: EIssueLayoutTypes }) => {
|
||||
await handleFormSubmit({
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
logo_props: formData.logo_props,
|
||||
filters: formData.filters,
|
||||
display_filters: formData.display_filters,
|
||||
display_filters: { ...formData.display_filters, layout: formData.layout },
|
||||
display_properties: formData.display_properties,
|
||||
} as IProjectView);
|
||||
|
||||
@@ -211,6 +212,18 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="layout"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<LayoutDropDown
|
||||
onChange={(selectedValue: EIssueLayoutTypes) => onChange(selectedValue)}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
|
||||
@@ -138,17 +138,19 @@ export const ISSUE_EXTRA_OPTIONS: {
|
||||
{ key: "show_empty_groups", title: "Show empty groups" }, // filter on front-end
|
||||
];
|
||||
|
||||
export const ISSUE_LAYOUT_MAP = {
|
||||
[EIssueLayoutTypes.LIST]: { key: EIssueLayoutTypes.LIST, title: "List Layout", icon: List },
|
||||
[EIssueLayoutTypes.KANBAN]: { key: EIssueLayoutTypes.KANBAN, title: "Kanban Layout", icon: Kanban },
|
||||
[EIssueLayoutTypes.CALENDAR]: { key: EIssueLayoutTypes.CALENDAR, title: "Calendar Layout", icon: Calendar },
|
||||
[EIssueLayoutTypes.SPREADSHEET]: { key: EIssueLayoutTypes.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
|
||||
[EIssueLayoutTypes.GANTT]: { key: EIssueLayoutTypes.GANTT, title: "Gantt Chart Layout", icon: GanttChartSquare },
|
||||
};
|
||||
|
||||
export const ISSUE_LAYOUTS: {
|
||||
key: EIssueLayoutTypes;
|
||||
title: string;
|
||||
icon: any;
|
||||
}[] = [
|
||||
{ key: EIssueLayoutTypes.LIST, title: "List Layout", icon: List },
|
||||
{ key: EIssueLayoutTypes.KANBAN, title: "Kanban Layout", icon: Kanban },
|
||||
{ key: EIssueLayoutTypes.CALENDAR, title: "Calendar Layout", icon: Calendar },
|
||||
{ key: EIssueLayoutTypes.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
|
||||
{ key: EIssueLayoutTypes.GANTT, title: "Gantt Chart Layout", icon: GanttChartSquare },
|
||||
];
|
||||
}[] = Object.values(ISSUE_LAYOUT_MAP);
|
||||
|
||||
export interface ILayoutDisplayFiltersOptions {
|
||||
filters: (keyof IIssueFilterOptions)[];
|
||||
|
||||
Reference in New Issue
Block a user