diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/CompanyProgressPicker.tsx
index f384dab36..ec5367d22 100644
--- a/front/src/modules/companies/components/CompanyProgressPicker.tsx
+++ b/front/src/modules/companies/components/CompanyProgressPicker.tsx
@@ -3,7 +3,7 @@ import { useRecoilState } from 'recoil';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
-import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
+import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
@@ -106,7 +106,7 @@ export const CompanyProgressPicker = ({
{selectedPipelineStage?.name}
-
- {
+ const resetViewEditMode = useResetRecoilState(viewEditModeState);
+
return (
}
@@ -28,6 +33,7 @@ export const BoardOptionsDropdown = ({
}
dropdownHotkeyScope={customHotkeyScope}
dropdownId={BoardOptionsDropdownKey}
+ onClickOutside={resetViewEditMode}
/>
);
};
diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
index 5c8fc800d..3580c0783 100644
--- a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
+++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx
@@ -1,13 +1,18 @@
import { useContext, useRef, useState } from 'react';
-import { useTheme } from '@emotion/react';
-import styled from '@emotion/styled';
-import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
+import {
+ useRecoilCallback,
+ useRecoilState,
+ useRecoilValue,
+ useResetRecoilState,
+} from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
+import { DropdownMenuInputContainer } from '@/ui/dropdown/components/DropdownMenuInputContainer';
+import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
@@ -16,7 +21,6 @@ import {
IconChevronLeft,
IconLayoutKanban,
IconPlus,
- IconSettings,
IconTag,
} from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
@@ -28,6 +32,7 @@ import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
+import { currentViewScopedSelector } from '@/ui/view-bar/states/selectors/currentViewScopedSelector';
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
@@ -45,10 +50,6 @@ export type BoardOptionsDropdownContentProps = {
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
};
-const StyledIconSettings = styled(IconSettings)`
- margin-right: ${({ theme }) => theme.spacing(1)};
-`;
-
type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages';
type ColumnForCreate = {
@@ -62,10 +63,7 @@ export const BoardOptionsDropdownContent = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownContentProps) => {
- const theme = useTheme();
-
- const BoardRecoilScopeContext =
- useContext(BoardContext).BoardRecoilScopeContext;
+ const { BoardRecoilScopeContext } = useContext(BoardContext);
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
@@ -93,7 +91,12 @@ export const BoardOptionsDropdownContent = ({
viewsByIdScopedSelector,
BoardRecoilScopeContext, // TODO: replace with ViewBarRecoilScopeContext
);
+ const currentView = useRecoilScopedValue(
+ currentViewScopedSelector,
+ BoardRecoilScopeContext,
+ );
const viewEditMode = useRecoilValue(viewEditModeState);
+ const resetViewEditMode = useResetRecoilState(viewEditModeState);
const handleStageSubmit = () => {
if (currentMenu !== 'stage-creation' || !stageInputRef?.current?.value)
@@ -148,6 +151,7 @@ export const BoardOptionsDropdownContent = ({
useScopedHotkeys(
Key.Escape,
() => {
+ resetViewEditMode();
closeDropdownButton();
},
customHotkeyScope.scope,
@@ -158,6 +162,7 @@ export const BoardOptionsDropdownContent = ({
() => {
handleStageSubmit();
handleViewNameSubmit();
+ resetViewEditMode();
closeDropdownButton();
},
customHotkeyScope.scope,
@@ -167,25 +172,24 @@ export const BoardOptionsDropdownContent = ({
{!currentMenu && (
<>
- {!!viewEditMode.mode ? (
+
- ) : (
-
-
- Settings
-
- )}
+
)}
{currentMenu === 'stage-creation' && (
- = {
+ title: 'UI/Board/Options/BoardOptionsDropdown',
+ component: BoardOptionsDropdown,
+ decorators: [
+ (Story, { parameters }) => (
+
+
+
+
+
+
+
+ ),
+ ComponentWithRecoilScopeDecorator,
+ ComponentDecorator,
+ ],
+ parameters: {
+ customRecoilScopeContext: CompanyBoardRecoilScopeContext,
+ },
+ args: {
+ customHotkeyScope: { scope: 'scope' },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ const dropdownButton = canvas.getByText('Options');
+
+ await userEvent.click(dropdownButton);
+ },
+};
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx
index 4003a3a35..aa564104d 100644
--- a/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx
@@ -1,43 +1,23 @@
-import { forwardRef, InputHTMLAttributes } from 'react';
import styled from '@emotion/styled';
+import { rgba } from '@/ui/theme/constants/colors';
import { textInputStyle } from '@/ui/theme/constants/effects';
-const StyledDropdownMenuInputContainer = styled.div`
- --vertical-padding: ${({ theme }) => theme.spacing(1)};
-
- align-items: center;
-
- display: flex;
- flex-direction: row;
- height: calc(36px - 2 * var(--vertical-padding));
- padding: var(--vertical-padding) 0;
-
- width: 100%;
-`;
-
-const StyledInput = styled.input`
+const StyledViewNameInput = styled.input`
${textInputStyle}
- font-size: ${({ theme }) => theme.font.size.sm};
+ border: 1px solid ${({ theme }) => theme.border.color.medium};
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ box-sizing: border-box;
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ height: 32px;
+ position: relative;
width: 100%;
- &[type='number']::-webkit-outer-spin-button,
- &[type='number']::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
- }
-
- &[type='number'] {
- -moz-appearance: textfield;
+ &:focus {
+ border-color: ${({ theme }) => theme.color.blue};
+ box-shadow: 0px 0px 0px 3px ${({ theme }) => rgba(theme.color.blue, 0.1)};
}
`;
-export const DropdownMenuInput = forwardRef<
- HTMLInputElement,
- InputHTMLAttributes
->((props, ref) => (
-
-
-
-));
+export { StyledViewNameInput as DropdownMenuInput };
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuInputContainer.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuInputContainer.tsx
new file mode 100644
index 000000000..6bc0c8e27
--- /dev/null
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuInputContainer.tsx
@@ -0,0 +1,9 @@
+import styled from '@emotion/styled';
+
+const StyledInputContainer = styled.div`
+ box-sizing: border-box;
+ padding: ${({ theme }) => theme.spacing(1)};
+ width: 100%;
+`;
+
+export { StyledInputContainer as DropdownMenuInputContainer };
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSearchInput.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuSearchInput.tsx
new file mode 100644
index 000000000..6e6b9abb3
--- /dev/null
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuSearchInput.tsx
@@ -0,0 +1,43 @@
+import { forwardRef, InputHTMLAttributes } from 'react';
+import styled from '@emotion/styled';
+
+import { textInputStyle } from '@/ui/theme/constants/effects';
+
+const StyledDropdownMenuSearchInputContainer = styled.div`
+ --vertical-padding: ${({ theme }) => theme.spacing(1)};
+
+ align-items: center;
+
+ display: flex;
+ flex-direction: row;
+ height: calc(36px - 2 * var(--vertical-padding));
+ padding: var(--vertical-padding) 0;
+
+ width: 100%;
+`;
+
+const StyledInput = styled.input`
+ ${textInputStyle}
+
+ font-size: ${({ theme }) => theme.font.size.sm};
+ width: 100%;
+
+ &[type='number']::-webkit-outer-spin-button,
+ &[type='number']::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ &[type='number'] {
+ -moz-appearance: textfield;
+ }
+`;
+
+export const DropdownMenuSearchInput = forwardRef<
+ HTMLInputElement,
+ InputHTMLAttributes
+>((props, ref) => (
+
+
+
+));
diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
index 4d7c805c7..b9c24d68c 100644
--- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
+++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
@@ -1,8 +1,7 @@
import { useState } from 'react';
import styled from '@emotion/styled';
-import type { Meta, StoryObj } from '@storybook/react';
+import type { Decorator, Meta, StoryObj } from '@storybook/react';
-import { IconPlus, IconUser } from '@/ui/icon';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar';
@@ -12,6 +11,8 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DropdownMenuHeader } from '../DropdownMenuHeader';
import { DropdownMenuInput } from '../DropdownMenuInput';
+import { DropdownMenuInputContainer } from '../DropdownMenuInputContainer';
+import { DropdownMenuSearchInput } from '../DropdownMenuSearchInput';
import { StyledDropdownMenu } from '../StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
@@ -23,7 +24,9 @@ const meta: Meta = {
decorators: [ComponentDecorator],
argTypes: {
as: { table: { disable: true } },
+ children: { control: false },
theme: { table: { disable: true } },
+ width: { type: 'number', defaultValue: undefined },
},
};
@@ -122,21 +125,22 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
};
const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
- const [selectedItems, setSelectedItems] = useState([]);
+ const [selectedItemsById, setSelectedItemsById] = useState<
+ Record
+ >({});
return (
<>
{mockSelectArray.map((item) => (
{
- if (checked) {
- setSelectedItems([...selectedItems, item.id]);
- } else {
- setSelectedItems(selectedItems.filter((id) => id !== item.id));
- }
- }}
+ selected={selectedItemsById[item.id]}
+ onSelectChange={(checked) =>
+ setSelectedItemsById((previous) => ({
+ ...previous,
+ [item.id]: checked,
+ }))
+ }
avatar={
hasAvatar ? (
{
);
};
+const WithContentBelowDecorator: Decorator = (Story) => (
+
+
+
+
+
+
+);
+
export const Empty: Story = {
- render: (args) => (
-
-
-
- ),
-};
-
-export const WithContentBelow: Story = {
- ...Empty,
- decorators: [
- (Story) => (
-
-
-
-
-
-
- ),
- ],
-};
-
-export const SimpleMenuItem: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
- {mockSelectArray.map(({ name }) => (
-
- ))}
-
-
- ),
+ args: { children: },
};
export const WithHeaders: Story = {
- ...WithContentBelow,
- render: (args) => (
-
- Header
-
- Subheader 1
-
- {mockSelectArray.slice(0, 3).map(({ name }) => (
-
- ))}
-
-
- Subheader 2
-
- {mockSelectArray.slice(3).map(({ name }) => (
-
- ))}
-
-
- ),
-};
-
-export const WithIcons: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
- {mockSelectArray.map(({ name }) => (
-
- ))}
-
-
- ),
-};
-
-export const WithActions: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
- {mockSelectArray.map(({ name }, index) => (
-
- ))}
-
-
- ),
- parameters: {
- pseudo: { hover: ['.hover'] },
+ decorators: [WithContentBelowDecorator],
+ args: {
+ children: (
+ <>
+ Header
+
+ Subheader 1
+
+ {mockSelectArray.slice(0, 3).map(({ name }) => (
+
+ ))}
+
+
+ Subheader 2
+
+ {mockSelectArray.slice(3).map(({ name }) => (
+
+ ))}
+
+ >
+ ),
},
};
-export const LoadingMenu: Story = {
- ...WithContentBelow,
- render: () => (
-
-
-
-
-
-
-
- ),
+export const SearchWithLoadingMenu: Story = {
+ decorators: [WithContentBelowDecorator],
+ args: {
+ children: (
+ <>
+
+
+
+
+
+ >
+ ),
+ },
};
-export const Search: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
-
-
- {mockSelectArray.map(({ name }) => (
-
- ))}
-
-
- ),
-};
-
-export const SelectableMenuItem: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
-
-
-
- ),
+export const WithInput: Story = {
+ decorators: [WithContentBelowDecorator],
+ args: {
+ children: (
+ <>
+
+
+
+
+
+ {mockSelectArray.map(({ name }) => (
+
+ ))}
+
+ >
+ ),
+ },
};
export const SelectableMenuItemWithAvatar: Story = {
- ...WithContentBelow,
- render: (args) => (
-
+ decorators: [WithContentBelowDecorator],
+ args: {
+ children: (
-
- ),
-};
-
-export const CheckableMenuItem: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
-
-
-
- ),
+ ),
+ },
};
export const CheckableMenuItemWithAvatar: Story = {
- ...WithContentBelow,
- render: (args) => (
-
+ decorators: [WithContentBelowDecorator],
+ args: {
+ children: (
-
- ),
+ ),
+ },
};
diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenuInput.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenuInput.stories.tsx
new file mode 100644
index 000000000..9e3478b01
--- /dev/null
+++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenuInput.stories.tsx
@@ -0,0 +1,25 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
+
+import { DropdownMenuInput } from '../DropdownMenuInput';
+
+const meta: Meta = {
+ title: 'UI/Dropdown/DropdownMenuInput',
+ component: DropdownMenuInput,
+ decorators: [ComponentDecorator],
+ args: { defaultValue: 'Lorem ipsum' },
+ argTypes: {
+ as: { table: { disable: true } },
+ theme: { table: { disable: true } },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Focused: Story = {
+ args: { autoFocus: true },
+};
diff --git a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx
index f4308bfb5..00fddc91b 100644
--- a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx
+++ b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx
@@ -1,7 +1,7 @@
import { useRef } from 'react';
import debounce from 'lodash.debounce';
-import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
+import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
@@ -73,7 +73,7 @@ export const MultipleEntitySelect = <
return (
-
- theme.spacing(1)};
- width: 100%;
-`;
-
-const StyledViewNameInput = styled.input`
- ${textInputStyle}
-
- border: 1px solid ${({ theme }) => theme.border.color.medium};
- border-radius: ${({ theme }) => theme.border.radius.sm};
- box-sizing: border-box;
- font-weight: ${({ theme }) => theme.font.weight.medium};
- height: 32px;
- position: relative;
- width: 100%;
-
- &:focus {
- border-color: ${({ theme }) => theme.color.blue};
- box-shadow: 0px 0px 0px 3px ${({ theme }) => rgba(theme.color.blue, 0.1)};
- }
-`;
-
export const TableOptionsDropdownContent = () => {
const scopeId = useRecoilScopeId(TableRecoilScopeContext);
@@ -158,8 +134,8 @@ export const TableOptionsDropdownContent = () => {
{!currentMenu && (
<>
-
-
+ {
: currentView?.name
}
/>
-
+