diff --git a/frontend/src/component/project/Project/CreateProject/ChangeRequestTable.tsx b/frontend/src/component/project/Project/CreateProject/ChangeRequestTable.tsx
index 7e193055d6..b92c791c74 100644
--- a/frontend/src/component/project/Project/CreateProject/ChangeRequestTable.tsx
+++ b/frontend/src/component/project/Project/CreateProject/ChangeRequestTable.tsx
@@ -14,7 +14,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import KeyboardArrowDownOutlined from '@mui/icons-material/KeyboardArrowDownOutlined';
import { useTheme } from '@mui/material/styles';
-// import { PROJECT_CHANGE_REQUEST_WRITE } from '../../../../providers/AccessProvider/permissions';
const StyledBox = styled(Box)(({ theme }) => ({
padding: theme.spacing(1),
diff --git a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
index 95b292f6f4..520b9cf23e 100644
--- a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
+++ b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
@@ -15,7 +15,6 @@ import { GO_BACK } from 'constants/navigate';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { Button, styled } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
-import { useState } from 'react';
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
@@ -35,14 +34,10 @@ const CreateProject = () => {
projectName,
projectDesc,
projectMode,
- projectEnvironments,
- projectChangeRequestConfiguration,
setProjectMode,
setProjectId,
setProjectName,
setProjectDesc,
- setProjectEnvironments,
- updateProjectChangeRequestConfig,
getCreateProjectPayload,
clearErrors,
validateProjectId,
@@ -56,14 +51,6 @@ const CreateProject = () => {
return ;
}
- const generalDocumentation =
- 'Projects allows you to group feature flags together in the management UI.';
-
- const [documentation, setDocumentation] = useState(generalDocumentation);
-
- const clearDocumentationOverride = () =>
- setDocumentation(generalDocumentation);
-
const { createProject, loading } = useProjectApi();
const handleSubmit = async (e: Event) => {
diff --git a/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx b/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx
index 11568efa43..808d00a05c 100644
--- a/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx
+++ b/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx
@@ -60,14 +60,12 @@ export const CreateProjectDialog = ({
projectEnvironments,
projectChangeRequestConfiguration,
setProjectMode,
- setProjectId,
setProjectName,
setProjectDesc,
setProjectEnvironments,
updateProjectChangeRequestConfig,
getCreateProjectPayload,
clearErrors,
- validateProjectId,
validateName,
setProjectStickiness,
projectStickiness,
@@ -151,7 +149,6 @@ export const CreateProjectDialog = ({
projectId={projectId}
projectEnvironments={projectEnvironments}
setProjectEnvironments={setProjectEnvironments}
- setProjectId={setProjectId}
projectName={projectName}
projectStickiness={projectStickiness}
projectChangeRequestConfiguration={
@@ -166,9 +163,6 @@ export const CreateProjectDialog = ({
setProjectName={setProjectName}
projectDesc={projectDesc}
setProjectDesc={setProjectDesc}
- mode='Create'
- clearErrors={clearErrors}
- validateProjectId={validateProjectId}
overrideDocumentation={setDocumentation}
clearDocumentationOverride={clearDocumentationOverride}
>
diff --git a/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx b/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx
index c10664969a..b5ed31dbb1 100644
--- a/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx
+++ b/frontend/src/component/project/Project/CreateProject/NewProjectForm.tsx
@@ -3,7 +3,7 @@ import Input from 'component/common/Input/Input';
import type { ProjectMode } from '../hooks/useProjectEnterpriseSettingsForm';
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';
import {
- MultiselectList,
+ MultiSelectList,
SingleSelectList,
TableSelect,
} from './SelectionButton';
@@ -80,8 +80,6 @@ type FormProps = {
projectName: string;
projectDesc: string;
projectStickiness: string;
- featureLimit?: string;
- featureCount?: number;
projectMode: string;
projectEnvironments: Set;
projectChangeRequestConfiguration: Record<
@@ -90,10 +88,8 @@ type FormProps = {
>;
setProjectStickiness: React.Dispatch>;
setProjectEnvironments: (envs: Set) => void;
- setProjectId: React.Dispatch>;
setProjectName: React.Dispatch>;
setProjectDesc: React.Dispatch>;
- setFeatureLimit?: React.Dispatch>;
setProjectMode: React.Dispatch>;
updateProjectChangeRequestConfig: {
disableChangeRequests: (env: string) => void;
@@ -101,9 +97,6 @@ type FormProps = {
};
handleSubmit: (e: any) => void;
errors: { [key: string]: string };
- mode: 'Create' | 'Edit';
- clearErrors: () => void;
- validateProjectId: () => void;
overrideDocumentation: (args: { text: string; icon: ReactNode }) => void;
clearDocumentationOverride: () => void;
};
@@ -119,20 +112,14 @@ export const NewProjectForm: React.FC = ({
projectStickiness,
projectEnvironments,
projectChangeRequestConfiguration,
- featureLimit,
- featureCount,
projectMode,
setProjectMode,
setProjectEnvironments,
- setProjectId,
setProjectName,
setProjectDesc,
setProjectStickiness,
updateProjectChangeRequestConfig,
- setFeatureLimit,
errors,
- mode,
- clearErrors,
overrideDocumentation,
clearDocumentationOverride,
}) => {
@@ -183,6 +170,15 @@ export const NewProjectForm: React.FC = ({
: numberOfConfiguredChangeRequestEnvironments === 1
? `1 environment configured`
: 'Configure change requests';
+
+ const availableChangeRequestEnvironments = (
+ projectEnvironments.size === 0
+ ? activeEnvironments
+ : activeEnvironments.filter((env) =>
+ projectEnvironments.has(env.name),
+ )
+ ).map(({ name, type }) => ({ name, type }));
+
return (
{
@@ -235,7 +231,7 @@ export const NewProjectForm: React.FC = ({
- ({
@@ -316,15 +312,9 @@ export const NewProjectForm: React.FC = ({
description={
selectionButtonData.changeRequests.text
}
- disabled={projectEnvironments.size === 0}
- activeEnvironments={activeEnvironments
- .filter((env) =>
- projectEnvironments.has(env.name),
- )
- .map((env) => ({
- name: env.name,
- type: env.type,
- }))}
+ activeEnvironments={
+ availableChangeRequestEnvironments
+ }
updateProjectChangeRequestConfiguration={
updateProjectChangeRequestConfig
}
diff --git a/frontend/src/component/project/Project/CreateProject/SelectionButton.styles.tsx b/frontend/src/component/project/Project/CreateProject/SelectionButton.styles.tsx
index 53c0517645..f58d97e116 100644
--- a/frontend/src/component/project/Project/CreateProject/SelectionButton.styles.tsx
+++ b/frontend/src/component/project/Project/CreateProject/SelectionButton.styles.tsx
@@ -74,3 +74,9 @@ export const ScrollContainer = styled('div')(({ theme }) => ({
width: '100%',
overflow: 'auto',
}));
+
+export const ButtonLabel = styled('span', {
+ shouldForwardProp: (prop) => prop !== 'labelWidth',
+})<{ labelWidth?: string }>(({ labelWidth }) => ({
+ width: labelWidth || 'unset',
+}));
diff --git a/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx b/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx
index e8b1754c4f..2539542e38 100644
--- a/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx
+++ b/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx
@@ -1,14 +1,14 @@
import Search from '@mui/icons-material/Search';
import { v4 as uuidv4 } from 'uuid';
import {
- Box,
- Button,
- InputAdornment,
- List,
- ListItemText,
- styled,
-} from '@mui/material';
-import { type FC, type ReactNode, useRef, useState, useMemo } from 'react';
+ type FC,
+ type ReactNode,
+ useRef,
+ useState,
+ useMemo,
+ type PropsWithChildren,
+} from 'react';
+import { Box, Button, InputAdornment, List, ListItemText } from '@mui/material';
import {
StyledCheckbox,
StyledDropdown,
@@ -17,7 +17,7 @@ import {
StyledDropdownSearch,
TableSearchInput,
HiddenDescription,
- ScrollContainer,
+ ButtonLabel,
} from './SelectionButton.styles';
import { ChangeRequestTable } from './ChangeRequestTable';
@@ -94,24 +94,30 @@ type CombinedSelectProps = {
description: string; // visually hidden, for assistive tech
};
-const CombinedSelect: FC = ({
- options,
- onChange,
+const CombinedSelect: FC<
+ PropsWithChildren<{
+ button: { label: string; icon: ReactNode; labelWidth?: string };
+ onOpen?: () => void;
+ onClose?: () => void;
+ description: string;
+ preventOpen?: boolean;
+ anchorEl: HTMLDivElement | null | undefined;
+ setAnchorEl: (el: HTMLDivElement | null | undefined) => void;
+ }>
+> = ({
button,
- search,
- multiselect,
onOpen = () => {},
onClose = () => {},
description,
+ children,
+ preventOpen,
+ anchorEl,
+ setAnchorEl,
}) => {
const ref = useRef(null);
- const [anchorEl, setAnchorEl] = useState();
- const [searchText, setSearchText] = useState('');
const descriptionId = uuidv4();
- const [recentlyClosed, setRecentlyClosed] = useState(false);
const open = () => {
- setSearchText('');
setAnchorEl(ref.current);
onOpen();
};
@@ -121,30 +127,6 @@ const CombinedSelect: FC = ({
onClose();
};
- const onSelection = (selected: string) => {
- onChange(selected);
- if (!multiselect) {
- handleClose();
- setRecentlyClosed(true);
- // this is a hack to prevent the button from being
- // auto-clicked after you select an item by pressing enter
- // in the search bar for single-select lists.
- setTimeout(() => setRecentlyClosed(false), 1);
- }
- };
-
- const { listRefs, handleSelection } = useSelectionManagement({
- handleToggle: (selected: string) => () => onSelection(selected),
- });
-
- const filteredOptions = options?.filter((option) =>
- option.label.toLowerCase().includes(searchText.toLowerCase()),
- );
-
- const ButtonLabel = styled('span')(() => ({
- width: button.labelWidth || 'unset',
- }));
-
return (
<>
@@ -153,12 +135,14 @@ const CombinedSelect: FC = ({
color='primary'
startIcon={button.icon}
onClick={() => {
- if (!recentlyClosed) {
+ if (!preventOpen) {
open();
}
}}
>
- {button.label}
+
+ {button.label}
+
= ({
{description}
- setSearchText(event.target.value)}
- label={search.label}
- hideLabel
- placeholder={search.placeholder}
- autoFocus
- InputProps={{
- startAdornment: (
-
-
-
- ),
- }}
- inputRef={(el) => {
- listRefs.current[0] = el;
- }}
- onKeyDown={(event) =>
- handleSelection(event, 0, filteredOptions)
- }
- />
-
- {filteredOptions.map((option, index) => {
- const labelId = `checkbox-list-label-${option.value}`;
-
- return (
- {
- onSelection(option.value);
- }}
- ref={(el) => {
- listRefs.current[index + 1] = el;
- }}
- onKeyDown={(event) =>
- handleSelection(
- event,
- index + 1,
- filteredOptions,
- )
- }
- >
- {multiselect ? (
-
- ) : null}
-
-
- );
- })}
-
+ {children}
>
);
};
-type MultiselectListProps = Pick<
- CombinedSelectProps,
- 'options' | 'button' | 'search' | 'onOpen' | 'onClose' | 'description'
-> & {
- selectedOptions: Set;
- onChange: (values: Set) => void;
-};
-
-export const MultiselectList: FC = ({
- selectedOptions,
+const DropdownList: FC = ({
+ options,
onChange,
- ...rest
+ search,
+ multiselect,
}) => {
- // todo: add "select all" and "deselect all"
+ const [searchText, setSearchText] = useState('');
- const handleToggle = (value: string) => {
- if (selectedOptions.has(value)) {
- selectedOptions.delete(value);
- } else {
- selectedOptions.add(value);
- }
-
- onChange(new Set(selectedOptions));
+ const onSelection = (selected: string) => {
+ onChange(selected);
};
+ const { listRefs, handleSelection } = useSelectionManagement({
+ handleToggle: (selected: string) => () => onSelection(selected),
+ });
+
+ const filteredOptions = options?.filter((option) =>
+ option.label.toLowerCase().includes(searchText.toLowerCase()),
+ );
+
return (
-
+ <>
+ setSearchText(event.target.value)}
+ label={search.label}
+ hideLabel
+ placeholder={search.placeholder}
+ autoFocus
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ inputRef={(el) => {
+ listRefs.current[0] = el;
+ }}
+ onKeyDown={(event) =>
+ handleSelection(event, 0, filteredOptions)
+ }
+ />
+
+ {filteredOptions.map((option, index) => {
+ const labelId = `checkbox-list-label-${option.value}`;
+
+ return (
+ {
+ onSelection(option.value);
+ }}
+ ref={(el) => {
+ listRefs.current[index + 1] = el;
+ }}
+ onKeyDown={(event) =>
+ handleSelection(
+ event,
+ index + 1,
+ filteredOptions,
+ )
+ }
+ >
+ {multiselect ? (
+
+ ) : null}
+
+
+ );
+ })}
+
+ >
);
};
@@ -301,8 +273,73 @@ type SingleSelectListProps = Pick<
| 'description'
>;
-export const SingleSelectList: FC = (props) => {
- return ;
+export const SingleSelectList: FC = ({
+ onChange,
+ ...props
+}) => {
+ const [anchorEl, setAnchorEl] = useState();
+ const [recentlyClosed, setRecentlyClosed] = useState(false);
+
+ const handleChange = (value: any) => {
+ onChange(value);
+ setAnchorEl(null);
+ props.onClose && props.onClose();
+
+ setRecentlyClosed(true);
+ // this is a hack to prevent the button from being
+ // auto-clicked after you select an item by pressing enter
+ // in the search bar for single-select lists.
+ setTimeout(() => setRecentlyClosed(false), 1);
+ };
+
+ return (
+
+
+
+ );
+};
+
+type MultiselectListProps = Pick<
+ CombinedSelectProps,
+ 'options' | 'button' | 'search' | 'onOpen' | 'onClose' | 'description'
+> & {
+ selectedOptions: Set;
+ onChange: (values: Set) => void;
+};
+
+export const MultiSelectList: FC = ({
+ selectedOptions,
+ onChange,
+ ...rest
+}) => {
+ const [anchorEl, setAnchorEl] = useState();
+
+ const handleToggle = (value: string) => {
+ if (selectedOptions.has(value)) {
+ selectedOptions.delete(value);
+ } else {
+ selectedOptions.add(value);
+ }
+
+ onChange(new Set(selectedOptions));
+ };
+
+ return (
+
+
+
+ );
};
type TableSelectProps = Pick<
@@ -321,17 +358,17 @@ type TableSelectProps = Pick<
string,
{ requiredApprovals: number }
>;
- disabled: boolean;
};
+
export const TableSelect: FC = ({
button,
- disabled,
search,
projectChangeRequestConfiguration,
updateProjectChangeRequestConfiguration,
activeEnvironments,
onOpen = () => {},
onClose = () => {},
+ ...props
}) => {
const configured = useMemo(() => {
return Object.fromEntries(
@@ -365,21 +402,9 @@ export const TableSelect: FC = ({
updateProjectChangeRequestConfiguration.disableChangeRequests(name);
};
- const ref = useRef(null);
const [anchorEl, setAnchorEl] = useState();
const [searchText, setSearchText] = useState('');
- const open = () => {
- setSearchText('');
- setAnchorEl(ref.current);
- onOpen();
- };
-
- const handleClose = () => {
- setAnchorEl(null);
- onClose();
- };
-
const filteredEnvs = tableEnvs.filter((env) =>
env.name.toLowerCase().includes(searchText.toLowerCase()),
);
@@ -399,66 +424,36 @@ export const TableSelect: FC = ({
}
};
- const ButtonLabel = styled('span')(() => ({
- width: button.labelWidth || 'unset',
- }));
-
return (
- <>
-
-
-
-
+ setSearchText(event.target.value)}
+ hideLabel
+ label={search.label}
+ placeholder={search.placeholder}
+ autoFocus
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
}}
- transformOrigin={{
- vertical: 'top',
- horizontal: 'left',
- }}
- >
-
- setSearchText(event.target.value)}
- hideLabel
- label={search.label}
- placeholder={search.placeholder}
- autoFocus
- InputProps={{
- startAdornment: (
-
-
-
- ),
- }}
- onKeyDown={toggleTopItem}
- />
-
-
-
-
-
- >
+ onKeyDown={toggleTopItem}
+ />
+
+
);
};