Removed last old DropdownButton (#1573)

* Removed last old DropdownButton

* Update front/src/modules/ui/view-bar/components/SingleEntityFilterDropdownButton.tsx

Co-authored-by: Thaïs <guigon.thais@gmail.com>

* Fix CI

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Thaïs <guigon.thais@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-09-15 00:33:45 +02:00
committed by GitHub
parent 2461a387ce
commit 70c0fd4e0d
16 changed files with 204 additions and 304 deletions

View File

@ -11,13 +11,8 @@ jobs:
REACT_APP_SERVER_BASE_URL: http://localhost:3000 REACT_APP_SERVER_BASE_URL: http://localhost:3000
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
if: github.event_name == 'push'
with: with:
ref: ${{ github.head_ref || github.ref_name }} ref: ${{ github.head_ref || github.ref_name }}
- uses: actions/checkout@v3
if: github.event_name == 'pull_request_target'
with:
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:

View File

@ -1,16 +1,13 @@
import { useEffect, useRef } from 'react'; import { useRef } from 'react';
import { Keys } from 'react-hotkeys-hook'; import { Keys } from 'react-hotkeys-hook';
import { flip, offset, Placement, useFloating } from '@floating-ui/react'; import { flip, offset, Placement, useFloating } from '@floating-ui/react';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { HotkeyEffect } from '../../utilities/hotkey/components/HotkeyEffect'; import { HotkeyEffect } from '../../utilities/hotkey/components/HotkeyEffect';
import { useDropdownButton } from '../hooks/useDropdownButton'; import { useDropdownButton } from '../hooks/useDropdownButton';
import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/dropdownButtonCustomHotkeyScopeScopedFamilyState'; import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
type OwnProps = { type OwnProps = {
buttonComponents: JSX.Element | JSX.Element[]; buttonComponents: JSX.Element | JSX.Element[];
@ -57,22 +54,10 @@ export function DropdownButton({
}, },
}); });
const [dropdownButtonCustomHotkeyScope, setDropdownButtonCustomHotkeyScope] = useInternalHotkeyScopeManagement({
useRecoilScopedFamilyState( dropdownId,
dropdownButtonCustomHotkeyScopeScopedFamilyState,
dropdownId,
DropdownRecoilScopeContext,
);
useEffect(() => {
if (!isDeeplyEqual(dropdownButtonCustomHotkeyScope, dropdownHotkeyScope)) {
setDropdownButtonCustomHotkeyScope(dropdownHotkeyScope);
}
}, [
dropdownHotkeyScope, dropdownHotkeyScope,
dropdownButtonCustomHotkeyScope, });
setDropdownButtonCustomHotkeyScope,
]);
return ( return (
<div ref={containerRef}> <div ref={containerRef}>

View File

@ -0,0 +1,27 @@
import styled from '@emotion/styled';
type StyledDropdownButtonProps = {
isUnfolded: boolean;
isActive?: boolean;
};
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ isActive, theme, color }) =>
color ?? (isActive ? theme.color.blue : 'none')};
cursor: pointer;
display: flex;
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
padding: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
user-select: none;
&:hover {
filter: brightness(0.95);
}
`;

View File

@ -1,7 +1,7 @@
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState'; import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState';
import { dropdownButtonCustomHotkeyScopeScopedFamilyState } from '../states/dropdownButtonCustomHotkeyScopeScopedFamilyState'; import { dropdownButtonHotkeyScopeScopedFamilyState } from '../states/dropdownButtonHotkeyScopeScopedFamilyState';
import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState'; import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButtonOpenScopedFamilyState';
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
@ -18,8 +18,8 @@ export function useDropdownButton({ dropdownId }: { dropdownId: string }) {
DropdownRecoilScopeContext, DropdownRecoilScopeContext,
); );
const [dropdownButtonCustomHotkeyScope] = useRecoilScopedFamilyState( const [dropdownButtonHotkeyScope] = useRecoilScopedFamilyState(
dropdownButtonCustomHotkeyScopeScopedFamilyState, dropdownButtonHotkeyScopeScopedFamilyState,
dropdownId, dropdownId,
DropdownRecoilScopeContext, DropdownRecoilScopeContext,
); );
@ -32,10 +32,10 @@ export function useDropdownButton({ dropdownId }: { dropdownId: string }) {
function openDropdownButton() { function openDropdownButton() {
setIsDropdownButtonOpen(true); setIsDropdownButtonOpen(true);
if (dropdownButtonCustomHotkeyScope) { if (dropdownButtonHotkeyScope) {
setHotkeyScopeAndMemorizePreviousScope( setHotkeyScopeAndMemorizePreviousScope(
dropdownButtonCustomHotkeyScope.scope, dropdownButtonHotkeyScope.scope,
dropdownButtonCustomHotkeyScope.customScopes, dropdownButtonHotkeyScope.customScopes,
); );
} }
} }

View File

@ -0,0 +1,33 @@
import { useEffect } from 'react';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedFamilyState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedFamilyState';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { dropdownButtonHotkeyScopeScopedFamilyState } from '../states/dropdownButtonHotkeyScopeScopedFamilyState';
import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext';
export const useInternalHotkeyScopeManagement = ({
dropdownId,
dropdownHotkeyScope,
}: {
dropdownId: string;
dropdownHotkeyScope?: HotkeyScope;
}) => {
const [dropdownButtonHotkeyScope, setDropdownButtonHotkeyScope] =
useRecoilScopedFamilyState(
dropdownButtonHotkeyScopeScopedFamilyState,
dropdownId,
DropdownRecoilScopeContext,
);
useEffect(() => {
if (!isDeeplyEqual(dropdownButtonHotkeyScope, dropdownHotkeyScope)) {
setDropdownButtonHotkeyScope(dropdownHotkeyScope);
}
}, [
dropdownHotkeyScope,
dropdownButtonHotkeyScope,
setDropdownButtonHotkeyScope,
]);
};

View File

@ -2,10 +2,10 @@ import { atomFamily } from 'recoil';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
export const dropdownButtonCustomHotkeyScopeScopedFamilyState = atomFamily< export const dropdownButtonHotkeyScopeScopedFamilyState = atomFamily<
HotkeyScope | null | undefined, HotkeyScope | null | undefined,
string string
>({ >({
key: 'dropdownButtonCustomHotkeyScopeScopedState', key: 'dropdownButtonHotkeyScopeScopedFamilyState',
default: null, default: null,
}); });

View File

@ -4,8 +4,8 @@ import { RecoilState, useRecoilState } from 'recoil';
import { RecoilScopeContext } from '../states/RecoilScopeContext'; import { RecoilScopeContext } from '../states/RecoilScopeContext';
export function useRecoilScopedFamilyState<StateType>( export function useRecoilScopedFamilyState<StateType>(
recoilState: (param: string) => RecoilState<StateType>, recoilState: (familyUniqueId: string) => RecoilState<StateType>,
stateKey: string, uniqueIdInRecoilScope: string,
SpecificContext?: Context<string | null>, SpecificContext?: Context<string | null>,
) { ) {
const recoilScopeId = useContext(SpecificContext ?? RecoilScopeContext); const recoilScopeId = useContext(SpecificContext ?? RecoilScopeContext);
@ -13,9 +13,11 @@ export function useRecoilScopedFamilyState<StateType>(
if (!recoilScopeId) if (!recoilScopeId)
throw new Error( throw new Error(
`Using a scoped atom without a RecoilScope : ${ `Using a scoped atom without a RecoilScope : ${
recoilState(stateKey).key recoilState('').key
}, verify that you are using a RecoilScope with a specific context if you intended to do so.`, }, verify that you are using a RecoilScope with a specific context if you intended to do so.`,
); );
return useRecoilState<StateType>(recoilState(recoilScopeId + stateKey)); const familyUniqueId = recoilScopeId + uniqueIdInRecoilScope;
return useRecoilState<StateType>(recoilState(familyUniqueId));
} }

View File

@ -1,126 +0,0 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { DropdownMenuContainer } from './DropdownMenuContainer';
type DropdownButtonProps = {
anchor?: 'left' | 'right';
label: ReactNode;
isActive: boolean;
children?: ReactNode;
isUnfolded?: boolean;
Icon?: IconComponent;
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
resetState?: () => void;
hotkeyScope: string;
color?: string;
menuWidth?: `${string}px` | 'auto' | number;
};
const StyledDropdownButtonContainer = styled.div`
display: flex;
flex-direction: column;
`;
const StyledDropdownButtonIcon = styled.div`
display: flex;
justify-content: center;
margin-right: ${({ theme }) => theme.spacing(1)};
`;
type StyledDropdownButtonProps = {
isUnfolded: boolean;
isActive: boolean;
};
const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ isActive, theme, color }) =>
color ?? (isActive ? theme.color.blue : 'none')};
cursor: pointer;
display: flex;
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
padding: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
user-select: none;
&:hover {
filter: brightness(0.95);
}
`;
/**
*
* @deprecated use ui/dropdown/components/DropdownButton.tsx instead
*/
function DropdownButton({
anchor,
label,
isActive,
children,
isUnfolded = false,
onIsUnfoldedChange,
Icon,
hotkeyScope,
color,
menuWidth,
}: DropdownButtonProps) {
useScopedHotkeys(
[Key.Enter, Key.Escape],
() => {
onIsUnfoldedChange?.(false);
},
hotkeyScope,
[onIsUnfoldedChange],
);
const onButtonClick = () => {
onIsUnfoldedChange?.(!isUnfolded);
};
const onOutsideClick = () => {
onIsUnfoldedChange?.(false);
};
const theme = useTheme();
return (
<StyledDropdownButtonContainer>
<StyledDropdownButton
isUnfolded={isUnfolded}
onClick={onButtonClick}
isActive={isActive}
aria-selected={isActive}
color={color}
>
{Icon && (
<StyledDropdownButtonIcon>
{<Icon size={theme.icon.size.md} />}
</StyledDropdownButtonIcon>
)}
{label}
</StyledDropdownButton>
{isUnfolded && (
<DropdownMenuContainer
width={menuWidth}
anchor={anchor}
onClose={onOutsideClick}
>
{children}
</DropdownMenuContainer>
)}
</StyledDropdownButtonContainer>
);
}
export default DropdownButton;

View File

@ -1,15 +1,54 @@
import { Context } from 'react';
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { FilterDropdownId } from '../constants/FilterDropdownId'; import { FilterDropdownId } from '../constants/FilterDropdownId';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
export function MultipleFiltersButton() { type OwnProps = {
context: Context<string | null>;
};
export function MultipleFiltersButton({ context }: OwnProps) {
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
dropdownId: FilterDropdownId, dropdownId: FilterDropdownId,
}); });
const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
context,
);
const [, setFilterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
context,
);
const [, setFilterDropdownSearchInput] = useRecoilScopedState(
filterDropdownSearchInputScopedState,
context,
);
const [, setSelectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
context,
);
const resetState = () => {
setIsFilterDropdownOperandSelectUnfolded(false);
setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null);
setFilterDropdownSearchInput('');
};
function handleClick() { function handleClick() {
toggleDropdownButton(); toggleDropdownButton();
resetState();
} }
return ( return (

View File

@ -1,15 +1,9 @@
import { Context, useCallback, useEffect } from 'react'; import { Context } from 'react';
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { FilterDropdownId } from '../constants/FilterDropdownId'; import { FilterDropdownId } from '../constants/FilterDropdownId';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
import { MultipleFiltersButton } from './MultipleFiltersButton'; import { MultipleFiltersButton } from './MultipleFiltersButton';
import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent';
@ -23,52 +17,10 @@ export function MultipleFiltersDropdownButton({
context, context,
hotkeyScope, hotkeyScope,
}: MultipleFiltersDropdownButtonProps) { }: MultipleFiltersDropdownButtonProps) {
const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
context,
);
const [, setFilterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
context,
);
const [, setFilterDropdownSearchInput] = useRecoilScopedState(
filterDropdownSearchInputScopedState,
context,
);
const [, setSelectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
context,
);
const { isDropdownButtonOpen } = useDropdownButton({
dropdownId: FilterDropdownId,
});
const resetState = useCallback(() => {
setIsFilterDropdownOperandSelectUnfolded(false);
setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null);
setFilterDropdownSearchInput('');
}, [
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setFilterDropdownSearchInput,
setIsFilterDropdownOperandSelectUnfolded,
]);
useEffect(() => {
if (!isDropdownButtonOpen) {
resetState();
}
}, [isDropdownButtonOpen, resetState]);
return ( return (
<DropdownButton <DropdownButton
dropdownId={FilterDropdownId} dropdownId={FilterDropdownId}
buttonComponents={<MultipleFiltersButton />} buttonComponents={<MultipleFiltersButton context={context} />}
dropdownComponents={<MultipleFiltersDropdownContent context={context} />} dropdownComponents={<MultipleFiltersDropdownContent context={context} />}
dropdownHotkeyScope={hotkeyScope} dropdownHotkeyScope={hotkeyScope}
/> />

View File

@ -3,6 +3,7 @@ import React from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DropdownMenuContainer } from '@/ui/dropdown/components/DropdownMenuContainer';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
import { IconChevronDown } from '@/ui/icon'; import { IconChevronDown } from '@/ui/icon';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
@ -18,7 +19,6 @@ import { filtersScopedState } from '../states/filtersScopedState';
import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState'; import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
import { DropdownMenuContainer } from './DropdownMenuContainer';
import { FilterDropdownEntitySearchInput } from './FilterDropdownEntitySearchInput'; import { FilterDropdownEntitySearchInput } from './FilterDropdownEntitySearchInput';
import { FilterDropdownEntitySelect } from './FilterDropdownEntitySelect'; import { FilterDropdownEntitySelect } from './FilterDropdownEntitySelect';
import { GenericEntityFilterChip } from './GenericEntityFilterChip'; import { GenericEntityFilterChip } from './GenericEntityFilterChip';

View File

@ -5,13 +5,13 @@ import { Key } from 'ts-key-enum';
import { Button } from '@/ui/button/components/Button'; import { Button } from '@/ui/button/components/Button';
import { ButtonGroup } from '@/ui/button/components/ButtonGroup'; import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
import { DropdownMenuContainer } from '@/ui/dropdown/components/DropdownMenuContainer';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { IconChevronDown, IconPlus } from '@/ui/icon'; import { IconChevronDown, IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { DropdownMenuContainer } from '@/ui/view-bar/components/DropdownMenuContainer';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState'; import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';

View File

@ -35,7 +35,7 @@ export const ViewBar = ({
leftComponent={ leftComponent={
<ViewsDropdownButton <ViewsDropdownButton
onViewEditModeChange={openOptionsDropdownButton} onViewEditModeChange={openOptionsDropdownButton}
hotkeyScope={ViewsHotkeyScope.ListDropdown} hotkeyScope={{ scope: ViewsHotkeyScope.ListDropdown }}
scopeContext={scopeContext} scopeContext={scopeContext}
/> />
} }

View File

@ -1,17 +1,14 @@
import { import { type Context, type MouseEvent, useContext } from 'react';
type Context,
type MouseEvent,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
import { StyledDropdownButtonContainer } from '@/ui/dropdown/components/StyledDropdownButtonContainer';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { import {
IconChevronDown, IconChevronDown,
IconList, IconList,
@ -21,10 +18,10 @@ import {
} from '@/ui/icon'; } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme'; import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import DropdownButton from '@/ui/view-bar/components/DropdownButton';
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState'; import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState'; import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
@ -33,9 +30,9 @@ import { currentViewScopedSelector } from '@/ui/view-bar/states/selectors/curren
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState'; import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState'; import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState'; import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState';
import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope';
import { assertNotNull } from '~/utils/assert'; import { assertNotNull } from '~/utils/assert';
import { ViewsDropdownId } from '../constants/ViewsDropdownId';
import { ViewBarContext } from '../contexts/ViewBarContext'; import { ViewBarContext } from '../contexts/ViewBarContext';
import { useRemoveView } from '../hooks/useRemoveView'; import { useRemoveView } from '../hooks/useRemoveView';
@ -73,7 +70,7 @@ const StyledViewName = styled.span`
`; `;
export type ViewsDropdownButtonProps = { export type ViewsDropdownButtonProps = {
hotkeyScope: ViewsHotkeyScope; hotkeyScope: HotkeyScope;
onViewEditModeChange?: () => void; onViewEditModeChange?: () => void;
scopeContext: Context<string | null>; scopeContext: Context<string | null>;
}; };
@ -88,24 +85,24 @@ export const ViewsDropdownButton = ({
const { defaultViewName, onViewSelect } = useContext(ViewBarContext); const { defaultViewName, onViewSelect } = useContext(ViewBarContext);
const recoilScopeId = useContextScopeId(scopeContext); const recoilScopeId = useContextScopeId(scopeContext);
const [isUnfolded, setIsUnfolded] = useState(false);
const currentView = useRecoilScopedValue( const currentView = useRecoilScopedValue(
currentViewScopedSelector, currentViewScopedSelector,
scopeContext, scopeContext,
); );
const views = useRecoilScopedValue(viewsScopedState, scopeContext); const [views] = useRecoilScopedState(viewsScopedState, scopeContext);
const setViewEditMode = useSetRecoilState(viewEditModeState);
const { const { isDropdownButtonOpen, closeDropdownButton, toggleDropdownButton } =
goBackToPreviousHotkeyScope, useDropdownButton({
setHotkeyScopeAndMemorizePreviousScope, dropdownId: ViewsDropdownId,
} = usePreviousHotkeyScope(); });
const setViewEditMode = useSetRecoilState(viewEditModeState);
const handleViewSelect = useRecoilCallback( const handleViewSelect = useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
async (viewId: string) => { async (viewId: string) => {
await onViewSelect?.(viewId); await onViewSelect?.(viewId);
const savedFilters = await snapshot.getPromise( const savedFilters = await snapshot.getPromise(
savedFiltersFamilyState(viewId), savedFiltersFamilyState(viewId),
); );
@ -116,26 +113,26 @@ export const ViewsDropdownButton = ({
set(filtersScopedState(recoilScopeId), savedFilters); set(filtersScopedState(recoilScopeId), savedFilters);
set(sortsScopedState(recoilScopeId), savedSorts); set(sortsScopedState(recoilScopeId), savedSorts);
set(currentViewIdScopedState(recoilScopeId), viewId); set(currentViewIdScopedState(recoilScopeId), viewId);
setIsUnfolded(false); closeDropdownButton();
}, },
[onViewSelect, recoilScopeId], [onViewSelect, recoilScopeId, closeDropdownButton],
); );
const handleAddViewButtonClick = useCallback(() => { const handleAddViewButtonClick = () => {
setViewEditMode({ mode: 'create', viewId: undefined }); setViewEditMode({ mode: 'create', viewId: undefined });
onViewEditModeChange?.(); onViewEditModeChange?.();
setIsUnfolded(false); closeDropdownButton();
}, [setViewEditMode, onViewEditModeChange]); };
const handleEditViewButtonClick = useCallback( const handleEditViewButtonClick = (
(event: MouseEvent<HTMLButtonElement>, viewId: string) => { event: MouseEvent<HTMLButtonElement>,
event.stopPropagation(); viewId: string,
setViewEditMode({ mode: 'edit', viewId }); ) => {
onViewEditModeChange?.(); event.stopPropagation();
setIsUnfolded(false); setViewEditMode({ mode: 'edit', viewId });
}, onViewEditModeChange?.();
[setViewEditMode, onViewEditModeChange], closeDropdownButton();
); };
const { removeView } = useRemoveView({ scopeContext }); const { removeView } = useRemoveView({ scopeContext });
@ -146,24 +143,22 @@ export const ViewsDropdownButton = ({
event.stopPropagation(); event.stopPropagation();
await removeView(viewId); await removeView(viewId);
setIsUnfolded(false); closeDropdownButton();
}; };
useEffect(() => { const handleViewButtonClick = () => {
isUnfolded toggleDropdownButton();
? setHotkeyScopeAndMemorizePreviousScope(hotkeyScope) };
: goBackToPreviousHotkeyScope();
}, [
hotkeyScope,
goBackToPreviousHotkeyScope,
isUnfolded,
setHotkeyScopeAndMemorizePreviousScope,
]);
return ( return (
<DropdownButton <DropdownButton
label={ dropdownId={ViewsDropdownId}
<> dropdownHotkeyScope={hotkeyScope}
buttonComponents={
<StyledDropdownButtonContainer
isUnfolded={isDropdownButtonOpen}
onClick={handleViewButtonClick}
>
<StyledViewIcon size={theme.icon.size.md} /> <StyledViewIcon size={theme.icon.size.md} />
<StyledViewName> <StyledViewName>
{currentView?.name || defaultViewName} {currentView?.name || defaultViewName}
@ -171,47 +166,44 @@ export const ViewsDropdownButton = ({
<StyledDropdownLabelAdornments> <StyledDropdownLabelAdornments>
· {views.length} <IconChevronDown size={theme.icon.size.sm} /> · {views.length} <IconChevronDown size={theme.icon.size.sm} />
</StyledDropdownLabelAdornments> </StyledDropdownLabelAdornments>
</> </StyledDropdownButtonContainer>
} }
isActive={false} dropdownComponents={
isUnfolded={isUnfolded} <StyledDropdownMenu>
onIsUnfoldedChange={setIsUnfolded} <StyledDropdownMenuItemsContainer>
anchor="left" {views.map((view) => (
hotkeyScope={hotkeyScope} <MenuItem
menuWidth="auto" key={view.id}
> iconButtons={[
<StyledDropdownMenuItemsContainer> {
{views.map((view) => ( Icon: IconPencil,
<MenuItem
key={view.id}
iconButtons={[
{
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
},
views.length > 1
? {
Icon: IconTrash,
onClick: (event: MouseEvent<HTMLButtonElement>) => onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleDeleteViewButtonClick(event, view.id), handleEditViewButtonClick(event, view.id),
} },
: null, views.length > 1
].filter(assertNotNull)} ? {
onClick={() => handleViewSelect(view.id)} Icon: IconTrash,
LeftIcon={IconList} onClick: (event: MouseEvent<HTMLButtonElement>) =>
text={view.name} handleDeleteViewButtonClick(event, view.id),
/> }
))} : null,
</StyledDropdownMenuItemsContainer> ].filter(assertNotNull)}
<StyledDropdownMenuSeparator /> onClick={() => handleViewSelect(view.id)}
<StyledBoldDropdownMenuItemsContainer> LeftIcon={IconList}
<MenuItem text={view.name}
onClick={handleAddViewButtonClick} />
LeftIcon={IconPlus} ))}
text="Add view" </StyledDropdownMenuItemsContainer>
/> <StyledDropdownMenuSeparator />
</StyledBoldDropdownMenuItemsContainer> <StyledBoldDropdownMenuItemsContainer>
</DropdownButton> <MenuItem
onClick={handleAddViewButtonClick}
LeftIcon={IconPlus}
text="Add view"
/>
</StyledBoldDropdownMenuItemsContainer>
</StyledDropdownMenu>
}
/>
); );
}; };

View File

@ -0,0 +1 @@
export const ViewsDropdownId = 'views';