7338 refactor actionbar and contextmenu to use the context store (#7462)
Closes #7338
This commit is contained in:
@ -0,0 +1,61 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useBottomBarInternalHotkeyScopeManagement } from '@/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement';
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
const StyledContainerActionBar = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
bottom: 38px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
display: flex;
|
||||
height: 48px;
|
||||
width: max-content;
|
||||
left: 50%;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: auto;
|
||||
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type BottomBarProps = {
|
||||
bottomBarId: string;
|
||||
bottomBarHotkeyScopeFromParent: HotkeyScope;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const BottomBar = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
children,
|
||||
}: BottomBarProps) => {
|
||||
const isBottomBarOpen = useRecoilComponentValueV2(
|
||||
isBottomBarOpenedComponentState,
|
||||
bottomBarId,
|
||||
);
|
||||
|
||||
useBottomBarInternalHotkeyScopeManagement({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
});
|
||||
|
||||
if (!isBottomBarOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomBarInstanceContext.Provider value={{ instanceId: bottomBarId }}>
|
||||
<StyledContainerActionBar data-select-disable className="bottom-bar">
|
||||
{children}
|
||||
</StyledContainerActionBar>
|
||||
</BottomBarInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { IconPlus } from 'twenty-ui';
|
||||
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof BottomBar> = {
|
||||
title: 'UI/Layout/BottomBar/BottomBar',
|
||||
component: BottomBar,
|
||||
args: {
|
||||
bottomBarId: 'test',
|
||||
bottomBarHotkeyScopeFromParent: { scope: 'test' },
|
||||
children: (
|
||||
<StyledContainer>
|
||||
<Button title="Test 1" Icon={IconPlus} />
|
||||
<Button title="Test 2" Icon={IconPlus} />
|
||||
<Button title="Test 3" Icon={IconPlus} />
|
||||
</StyledContainer>
|
||||
),
|
||||
},
|
||||
argTypes: {
|
||||
bottomBarId: { control: false },
|
||||
bottomBarHotkeyScopeFromParent: { control: false },
|
||||
children: { control: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: 'test',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Closed: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,87 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useBottomBar = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeBottomBar = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(specificComponentId: string) => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
},
|
||||
[goBackToPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
const openBottomBar = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(specificComponentId: string, customHotkeyScope?: HotkeyScope) => {
|
||||
const bottomBarHotkeyScope = snapshot
|
||||
.getLoadable(
|
||||
bottomBarHotkeyComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
if (isDefined(customHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
customHotkeyScope.scope,
|
||||
customHotkeyScope.customScopes,
|
||||
);
|
||||
} else if (isDefined(bottomBarHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
bottomBarHotkeyScope.scope,
|
||||
bottomBarHotkeyScope.customScopes,
|
||||
);
|
||||
}
|
||||
},
|
||||
[setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
const toggleBottomBar = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(specificComponentId: string) => {
|
||||
const isBottomBarOpen = snapshot
|
||||
.getLoadable(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isBottomBarOpen) {
|
||||
closeBottomBar(specificComponentId);
|
||||
} else {
|
||||
openBottomBar(specificComponentId);
|
||||
}
|
||||
},
|
||||
[closeBottomBar, openBottomBar],
|
||||
);
|
||||
|
||||
return {
|
||||
closeBottomBar,
|
||||
openBottomBar,
|
||||
toggleBottomBar,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useBottomBarInternalHotkeyScopeManagement = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
}: {
|
||||
bottomBarId?: string;
|
||||
bottomBarHotkeyScopeFromParent?: HotkeyScope;
|
||||
}) => {
|
||||
const [bottomBarHotkeyScope, setBottomBarHotkeyScope] =
|
||||
useRecoilComponentStateV2(bottomBarHotkeyComponentState, bottomBarId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(bottomBarHotkeyScopeFromParent, bottomBarHotkeyScope)) {
|
||||
setBottomBarHotkeyScope(bottomBarHotkeyScopeFromParent);
|
||||
}
|
||||
}, [
|
||||
bottomBarHotkeyScope,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
setBottomBarHotkeyScope,
|
||||
]);
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const bottomBarHotkeyComponentState = createComponentStateV2<
|
||||
HotkeyScope | null | undefined
|
||||
>({
|
||||
key: 'bottomBarHotkeyComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const BottomBarInstanceContext = createComponentInstanceContext();
|
||||
@ -0,0 +1,8 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const isBottomBarOpenedComponentState = createComponentStateV2<boolean>({
|
||||
key: 'isBottomBarOpenedComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
Reference in New Issue
Block a user