d81f006979
## Summary Rename all Jotai-based state management hooks and utilities to a consistent naming convention that: 1. Removes legacy Recoil naming (`useRecoilXxxV2`, `createXxxV2`) 2. Always includes `Atom` in the name to distinguish from jotai native hooks 3. Follows a consistent ordering: `Atom` → `Component` → `Family` → `Type` (`State`/`Selector`) 4. Includes the type qualifier (`State` or `Selector`) in all value-reading hooks to avoid naming conflicts with jotai's native `useAtomValue` ## Naming Convention **Hooks**: `use[Set]Atom[Component][Family][State|Selector][Value|State|CallbackState]` **Utils**: `createAtom[Component][Family][State|Selector]` ## Changes ### Hooks renamed (definition files + all usages across ~1,500 files): | Old Name (Recoil) | Intermediate (Atom) | Final Name | |---|---|---| | `useRecoilValueV2` | `useAtomValue` | **`useAtomStateValue`** | | `useRecoilStateV2` | `useAtomState` | `useAtomState` | | `useSetRecoilStateV2` | `useSetAtomState` | `useSetAtomState` | | `useFamilyRecoilValueV2` | `useFamilyAtomValue` | **`useAtomFamilyStateValue`** | | `useSetFamilyRecoilStateV2` | `useSetFamilyAtomState` | **`useSetAtomFamilyState`** | | `useFamilySelectorValueV2` | `useFamilySelectorValue` | **`useAtomFamilySelectorValue`** | | `useFamilySelectorStateV2` | `useFamilySelectorState` | **`useAtomFamilySelectorState`** | | `useRecoilComponentValueV2` | `useAtomComponentValue` | **`useAtomComponentStateValue`** | | `useRecoilComponentStateV2` | `useAtomComponentState` | `useAtomComponentState` | | `useSetRecoilComponentStateV2` | `useSetAtomComponentState` | `useSetAtomComponentState` | | `useRecoilComponentFamilyValueV2` | `useAtomComponentFamilyValue` | **`useAtomComponentFamilyStateValue`** | | `useRecoilComponentFamilyStateV2` | `useAtomComponentFamilyState` | `useAtomComponentFamilyState` | | `useSetRecoilComponentFamilyStateV2` | `useSetAtomComponentFamilyState` | `useSetAtomComponentFamilyState` | | `useRecoilComponentSelectorValueV2` | `useAtomComponentSelectorValue` | `useAtomComponentSelectorValue` | | `useRecoilComponentFamilySelectorValueV2` | `useAtomComponentFamilySelectorValue` | `useAtomComponentFamilySelectorValue` | | `useRecoilComponentStateCallbackStateV2` | `useAtomComponentStateCallbackState` | `useAtomComponentStateCallbackState` | | `useRecoilComponentSelectorCallbackStateV2` | `useAtomComponentSelectorCallbackState` | `useAtomComponentSelectorCallbackState` | | `useRecoilComponentFamilyStateCallbackStateV2` | `useAtomComponentFamilyStateCallbackState` | `useAtomComponentFamilyStateCallbackState` | | `useRecoilComponentFamilySelectorCallbackStateV2` | `useAtomComponentFamilySelectorCallbackState` | `useAtomComponentFamilySelectorCallbackState` | ### Utilities renamed: | Old Name (Recoil) | Final Name | |---|---| | `createStateV2` | **`createAtomState`** | | `createFamilyStateV2` | **`createAtomFamilyState`** | | `createSelectorV2` | **`createAtomSelector`** | | `createFamilySelectorV2` | **`createAtomFamilySelector`** | | `createWritableSelectorV2` | **`createAtomWritableSelector`** | | `createWritableFamilySelectorV2` | **`createAtomWritableFamilySelector`** | | `createComponentStateV2` | **`createAtomComponentState`** | | `createComponentFamilyStateV2` | **`createAtomComponentFamilyState`** | | `createComponentSelectorV2` | **`createAtomComponentSelector`** | | `createComponentFamilySelectorV2` | **`createAtomComponentFamilySelector`** | ## Details - All definition files renamed to match new convention - All import paths and usages updated across the entire `twenty-front` package - Jotai's native `useAtomValue` is aliased as `useJotaiAtomValue` in the wrapper hook to avoid collision - Legacy Recoil files in `state/utils/` and `component-state/utils/` left untouched (separate naming scope) - Typecheck passes cleanly
195 lines
6.7 KiB
TypeScript
195 lines
6.7 KiB
TypeScript
import { CommandMenuBackButton } from '@/command-menu/components/CommandMenuBackButton';
|
|
import { CommandMenuPageInfo } from '@/command-menu/components/CommandMenuPageInfo';
|
|
import { CommandMenuTopBarInputFocusEffect } from '@/command-menu/components/CommandMenuTopBarInputFocusEffect';
|
|
import { CommandMenuTopBarRightCornerIcon } from '@/command-menu/components/CommandMenuTopBarRightCornerIcon';
|
|
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
|
import { COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE } from '@/command-menu/constants/CommandMenuSearchBarHeightMobile';
|
|
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
|
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
|
import { useCommandMenuContextChips } from '@/command-menu/hooks/useCommandMenuContextChips';
|
|
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
|
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
|
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
|
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
|
|
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
|
|
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
|
import { useTheme } from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { useRef } from 'react';
|
|
import { CommandMenuPages } from 'twenty-shared/types';
|
|
import { IconX } from 'twenty-ui/display';
|
|
import { IconButton } from 'twenty-ui/input';
|
|
import { useIsMobile } from 'twenty-ui/utilities';
|
|
|
|
const StyledInputContainer = styled.div<{ isMobile: boolean }>`
|
|
align-items: center;
|
|
background-color: ${({ theme }) => theme.background.secondary};
|
|
border: none;
|
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
border-radius: 0;
|
|
box-sizing: border-box;
|
|
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: ${({ theme }) => theme.font.size.lg};
|
|
height: ${({ isMobile }) =>
|
|
isMobile
|
|
? COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE
|
|
: COMMAND_MENU_SEARCH_BAR_HEIGHT}px;
|
|
margin: 0;
|
|
outline: none;
|
|
position: relative;
|
|
overflow: hidden;
|
|
|
|
padding: 0 ${({ theme }) => theme.spacing(COMMAND_MENU_SEARCH_BAR_PADDING)};
|
|
gap: ${({ theme }) => theme.spacing(4)};
|
|
flex-shrink: 0;
|
|
justify-content: space-between;
|
|
`;
|
|
|
|
const StyledInput = styled.input`
|
|
border: none;
|
|
border-radius: 0;
|
|
background-color: transparent;
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
font-size: ${({ theme }) => theme.font.size.md};
|
|
margin: 0;
|
|
outline: none;
|
|
height: 24px;
|
|
padding: 0;
|
|
flex: 1;
|
|
|
|
&::placeholder {
|
|
color: ${({ theme }) => theme.font.color.light};
|
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
}
|
|
`;
|
|
|
|
const StyledContentContainer = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex: 1;
|
|
gap: ${({ theme }) => theme.spacing(1)};
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
`;
|
|
|
|
export const CommandMenuTopBar = () => {
|
|
const [commandMenuSearch, setCommandMenuSearch] = useAtomState(
|
|
commandMenuSearchState,
|
|
);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const { t } = useLingui();
|
|
|
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
setCommandMenuSearch(event.target.value);
|
|
};
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
const { closeCommandMenu } = useCommandMenu();
|
|
|
|
const commandMenuPage = useAtomStateValue(commandMenuPageState);
|
|
|
|
const commandMenuNavigationStack = useAtomStateValue(
|
|
commandMenuNavigationStackState,
|
|
);
|
|
|
|
const theme = useTheme();
|
|
|
|
const { contextChips } = useCommandMenuContextChips();
|
|
|
|
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
|
|
const { removeFocusItemFromFocusStackById } =
|
|
useRemoveFocusItemFromFocusStackById();
|
|
|
|
const handleInputFocus = () => {
|
|
pushFocusItemToFocusStack({
|
|
focusId: SIDE_PANEL_FOCUS_ID,
|
|
component: {
|
|
type: FocusComponentType.TEXT_INPUT,
|
|
instanceId: SIDE_PANEL_FOCUS_ID,
|
|
},
|
|
globalHotkeysConfig: {
|
|
enableGlobalHotkeysConflictingWithKeyboard: false,
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleInputBlur = () => {
|
|
removeFocusItemFromFocusStackById({
|
|
focusId: SIDE_PANEL_FOCUS_ID,
|
|
});
|
|
};
|
|
|
|
const canGoBack = commandMenuNavigationStack.length > 1;
|
|
|
|
const shouldShowCloseButton =
|
|
!isMobile && commandMenuNavigationStack.length === 1;
|
|
|
|
const shouldShowBackButton = canGoBack;
|
|
|
|
const lastChip = contextChips.at(-1);
|
|
|
|
return (
|
|
<StyledInputContainer isMobile={isMobile}>
|
|
<StyledContentContainer>
|
|
<AnimatePresence>
|
|
{shouldShowBackButton && (
|
|
<motion.div
|
|
exit={{ opacity: 0, width: 0 }}
|
|
transition={{
|
|
duration: theme.animation.duration.instant,
|
|
}}
|
|
>
|
|
<CommandMenuBackButton />
|
|
</motion.div>
|
|
)}
|
|
{shouldShowCloseButton && (
|
|
<motion.div
|
|
exit={{ opacity: 0, width: 0 }}
|
|
transition={{
|
|
duration: theme.animation.duration.instant,
|
|
}}
|
|
>
|
|
<IconButton
|
|
Icon={IconX}
|
|
size="small"
|
|
variant="tertiary"
|
|
onClick={closeCommandMenu}
|
|
/>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
{lastChip &&
|
|
commandMenuPage !== CommandMenuPages.Root &&
|
|
commandMenuPage !== CommandMenuPages.SearchRecords && (
|
|
<CommandMenuPageInfo pageChip={lastChip} />
|
|
)}
|
|
{(commandMenuPage === CommandMenuPages.Root ||
|
|
commandMenuPage === CommandMenuPages.SearchRecords) && (
|
|
<>
|
|
<StyledInput
|
|
data-testid={SIDE_PANEL_FOCUS_ID}
|
|
ref={inputRef}
|
|
value={commandMenuSearch}
|
|
placeholder={t`Type anything...`}
|
|
onChange={handleSearchChange}
|
|
onFocus={handleInputFocus}
|
|
onBlur={handleInputBlur}
|
|
/>
|
|
<CommandMenuTopBarInputFocusEffect inputRef={inputRef} />
|
|
</>
|
|
)}
|
|
</StyledContentContainer>
|
|
<CommandMenuTopBarRightCornerIcon />
|
|
</StyledInputContainer>
|
|
);
|
|
};
|