Fixes#12093
This bug was quite hard to fix because it was an issue with the
`AnimatePresence` component of the framer motion library.
After investigating the issue with @Devessier, here is what we
understood:
Since the modal component has an exit animation but wasn't wrapped
inside an `AnimatePresence` component, the animation seemed to never be
marked as complete when we closed the modal and the component did not
appear anymore but was still in the dom.
This caused an issue when closing the side panel because the state
cleanup function of the command menu is triggered when its closing
animation is complete. This cleanup function emits a right drawer close
event, which is listened by the record table row to update it's state.
The `onExitComplete` was never triggered because the exit animation of
the modal was never considered as complete, and since it's a children
animation of the command menu `AnimatePresence`, this animation was
never considered as complete either (see [PresenceChild
doc](https://github.com/motiondivision/motion/blob/main/packages/framer-motion/src/components/AnimatePresence/PresenceChild.tsx).
This caused the cleanup function to never be executed and the close
event to never be emitted, so the row stayed active.
Before:
https://github.com/user-attachments/assets/a165039b-6203-43d6-b992-dcfb4dfb8f2b
After:
https://github.com/user-attachments/assets/42eab2e8-62c9-4c25-85d6-78210d7ebe89
We previously used classnames to exclude elements from the click outside
listener.
With this PR we can now use `data-click-outside-id` instead of
`classNames` to target the elements we want to exclude from the click
outside listener.
We can also add `data-globally-prevent-click-outside` to a component to
globally prevent triggering click outside listeners for other
components. This attribute is especially useful for confirmation modals
and snackbar items.
Fixes#11785:
https://github.com/user-attachments/assets/318baa7e-0f82-4e3a-a447-bf981328462d
Fixes https://github.com/twentyhq/core-team-issues/issues/950
This issue was due to the memoization inside `useIsMatchingLocation`,
which was rerendered only if the pathname changed but not the search
params.
After discussion with @lucasbordeau, we decided to remove the hook
`useIsMatchingLocation` and to create an equivalent util function which
takes the location as an argument.
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
# Introduce focus stack to handle hotkeys
This PR introduces a focus stack to track the order in which the
elements are focused:
- Each focused element has a unique focus id
- When an element is focused, it is pushed on top of the stack
- When an element loses focus, we remove it from the stack
This focus stack is then used to determine which hotkeys are available.
The previous implementation lead to many regressions because of race
conditions, of wrong order of open and close operations and by
overwriting previous states. This implementation should be way more
robust than the previous one.
The new api can be incrementally implemented since it preserves
backwards compatibility by writing to the old hotkey scopes states.
For now, it has been implemented on the modal components.
To test this PR, verify that the shortcuts still work correctly,
especially for the modal components.
In this PR:
- deprecating listenClickOutside ComparePixel mode as this is not
accurate. We were using to avoid portal issue with CompareHtmlRef mode
but this is still an issue when portalled content overflows the
container.
- add ClickOutsideContext to specify excluded className so portal
children can use it easily (part of the tooling)
- fix stories
- remove avoidPortal from dropdown as this was not used
Fixes https://github.com/twentyhq/twenty/issues/12111
The bug occurred because in
https://github.com/twentyhq/twenty/pull/12062, I changed the click
outside mode of the modal from compare pixels to compare html ref. This
happens because the modal is in a portal, so the `compareHTMLRef`
doesn't work.
A bug already existed before but since the mode was compare pixel, it
only happened when a dropdown was overflowing from the modal:
https://github.com/user-attachments/assets/e34bfaca-dd21-46e5-a532-a66ba494889d
I commented the tests `CancelButtonClick`, and `ConfirmButtonClick`
because they don't work with compare pixel mode (the `userEvent.click()`
creates a `MouseEvent` with `clientX`=0 and `clientY`=0 so it triggers
the click outside listener even when the story tiggers a click on an
element inside a modal)
We should find a way to make the ClickOutsideMode `compareHTMLRef` work
with portals. I believe the `comparePixels` mode was used as a hacky way
to get around this problem (hacky because of the existing bug above).
# Modal API Refactoring
This PR refactors the modal system to use an imperative approach for
setting hotkey scopes, addressing race conditions that occurred with the
previous effect-based implementation.
Fixes#11986Closes#12087
## Key Changes:
- **New Modal API**: Introduced a `useModal` hook with `openModal`,
`closeModal`, and `toggleModal` functions, similar to the existing
dropdown API
- **Modal Identification**: Added a `modalId` prop to uniquely identify
modals
- **State Management**: Introduced `isModalOpenedComponentState` and
removed individual boolean state atoms (like
`isRemoveSortingModalOpenState`)
- **Modal Constants**: Added consistent modal ID constants (e.g.,
`FavoriteFolderDeleteModalId`, `RecordIndexRemoveSortingModalId`) for
better maintainability
- **Mount Effects**: Created mount effect components (like
`AuthModalMountEffect`) to handle initial modal opening where needed
## Implementation Details:
- Modified `Modal` and `ConfirmationModal` components to accept the new
`modalId` prop
- Added a component-state-based approach using
`ModalComponentInstanceContext` to track modal state
- Introduced imperative modal handlers that properly manage hotkey
scopes
- Components like `ActionModal` and `AttachmentList` now use the new
`useModal` hook for better control over modal state
## Benefits:
- **Race Condition Prevention**: Hotkey scopes are now set imperatively,
eliminating race conditions
- **Consistent API**: Modal and dropdown now share similar patterns,
improving developer experience
## Tests to do before merging:
1. Action Modals (Modal triggered by an action, for example the delete
action)
2. Auth Modal (`AuthModal.tsx` and `AuthModalMountEffect.tsx`)
- Test that auth modal opens automatically on mount
- Verify authentication flow works properly
3. Email Verification Sent Modal (in `SignInUp.tsx`)
- Verify this modal displays correctly
4. Attachment Preview Modal (in `AttachmentList.tsx`)
- Test opening preview modal by clicking on attachments
- Verify close, download functionality works
- Test modal navigation and interactions
5. Favorite Folder Delete Modal (`CurrentWorkspaceMemberFavorites.tsx`)
- Test deletion confirmation flow
- Check that modal opens when attempting to delete folders with
favorites
6. Record Board Remove Sorting Modal (`RecordBoard.tsx` using
`RecordIndexRemoveSortingModalId`)
- Test that modal appears when trying to drag records with sorting
enabled
- Verify sorting removal works correctly
7. Record Group Reorder Confirmation Modal
(`RecordGroupReorderConfirmationModal.tsx`)
- Test group reordering with sorting enabled
- Verify confirmation modal properly handles sorting removal
8. Confirmation Modal (base component used by several modals)
- Test all variants with different confirmation options
For each modal, verify:
- Opening/closing behavior
- Hotkey support (Esc to close, Enter to confirm where applicable)
- Click outside behavior
- Proper z-index stacking
- Any modal-specific functionality
After reading the blocknote documentation :
- we decided to increase to 100% the lines width
- we decided to reduce as much as possible inner padding
- we decided it's on the parent to decide the padding of the richtext
The two last points are recommended in a discussion on the project
blocknote. This way clicking on padding won't trigger weird behaviour on
Chrome.
Fixes
https://github.com/twentyhq/core-team-issues/issues/827#issuecomment-2842350359
- enrich response so the record is available in the step output. Today
this is available in the schema but only the id is set
- make the full record picker clickable instead of the arrow only
<img width="467" alt="Capture d’écran 2025-04-30 à 16 08 04"
src="https://github.com/user-attachments/assets/db74b9a6-7f1d-4e54-bf06-9be3d67ee398"
/>
This PR is refactoring a part of the ongoing filter refactor that was
blocking other refactor in that area.
Precisely, the dropdown filter that was used with the editable filter
chip was initialized by two conflicting useEffect, causing many unwanted
and hard to tackle bugs when modifying other places in the code that
used the same dropdown.
We also remove a difficult to maintain pattern around
onToggleColumnFilterComponentState, which was storing a click handler in
a state, we want to avoid this pattern.
The hook useHandleToggleColumnFilter is also removed and replaced by
useOpenRecordFilterChipFromTableHeader.
The code is now synchronous and starts from the user click event that is
triggered on a table cell header filter button click.
Also :
- Created a useSetEditableFilterChipDropdownStates that allows to
separate the code path of filter chip dropdown from the code path of
view bar global filter dropdown (will be continued in another refactor)
- Added useCreateEmptyFilterFromFieldMetadataItem to abstract empty
filter creation when opening a filter dropdown (will be used for other
refactor)
- Created a useOpenDropdownFromOutside hook that will also be used for
other refactor on filter
- Deleted EditableFilterDropdownButtonEffect
- Removed call to ViewBarFilterEffect (will be completely removed in
other refactors)
# Ability to navigate dropdown menus with keyboard
The aim of this PR is to improve accessibility by allowing the user to
navigate inside the dropdown menus with the keyboard.
This PR refactors the `SelectableList` and `SelectableListItem`
components to move the Enter event handling responsibility from
`SelectableList` to the individual `SelectableListItem` components.
Closes [512](https://github.com/twentyhq/core-team-issues/issues/512)
## Key Changes:
- All dropdowns are now navigable with arrow keys
## Technical Implementation:
- Each `SelectableListItem` now has direct access to its own `Enter` key
handler, improving component encapsulation
- Removed the central `Enter` key handler logic from `SelectableList`
- Added `SelectableList` and `SelectableListItem` to all `Dropdown`
components inside the app
- Updated all component implementations to adapt to the new pattern:
- Action menu components (`ActionDropdownItem`, `ActionListItem`)
- Command menu components
- Object filter, sort and options dropdowns
- Record picker components
- Select components
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
As per title:
- waitFor is a loop that waits for a condition to be filled, it should
be use to expect or in rare case to wait for element to be present in
the page (in most cases, you can use findByXXX)
- user actions should not be in this loop, otherwise they will be
triggered multiple times
Recoil-sync was causing issues with Firefox, replacing it with a simpler
mechanism to hydrate variables on page load
---------
Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
In this PR:
- Remove SignUpLoading blank screen by an empty dark overlay =>
VerifyEffect
- Add ModalContent from pages themselves instead of using it the Layout.
This allow for empty dark overlay without showing an empty modal with
padding
In this PR we introduce a generic way to close any open dropdown
idempotently, with the hook useCloseAnyOpenDropdown.
We also introduce a generic hook useExecuteTasksOnAnyLocationChange that
is called each time the page location changes.
This way we can close any open dropdown when the page location changes,
which fixes the original issue of having advanced filter dropdown
staying open between page changes.
Fixes https://github.com/twentyhq/core-team-issues/issues/659
closes#11195closes#11199
### Context
The yellow dots in the Settings Navigation Drawer (used to indicate
advanced settings) were being hidden due to ScrollWrapper's overflow
handling. This required both a fix for the visibility issue and an
improvement to the component structure.
### Changes
1. Keep scrolling logic of the MainNavigationDrawer and
SettingsNavigationDrawer in one place, and conditionally apply
`<StyledScrollableInnerContainer>` when isSettingsDrawer is true.
2. Fixed Yellow Dots Visibility
Added specific padding in NavigationDrawerScrollableContent to
accommodate yellow dots:
```
padding-left: ${theme.spacing(5)}; // Space for yellow dots
padding-right: ${theme.spacing(8)}; // Space for no-padding scroll
```
This ensures the yellow dots are visible while maintaining proper scroll behavior
3. Improved Component Composition
Using proper component composition instead of passing components as props
Components are now composed in a more React-idiomatic way:
```
<NavigationDrawer>
<NavigationDrawerScrollableContent>
<SettingsNavigationDrawerItems />
</NavigationDrawerScrollableContent>
<NavigationDrawerFixedContent>
<AdvancedSettingsToggle />
</NavigationDrawerFixedContent>
</NavigationDrawer>
```
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR fixes many small bugs around the recent hotkey scope refactor.
- Removed unused ActionBar files
- Created components CommandMenuOpenContainer and
KeyboardShortcutMenuOpenContent to avoid mounting listeners when not
needed
- Added DEFAULT_CELL_SCOPE where missing in some field inputs
- Called setHotkeyScopeAndMemorizePreviousScope instead of
setHotkeyScope in new useOpenFieldInputEditMode hook
- Broke down RecordTableBodyUnselectEffect into multiple simpler effect
components that are mounted only when needed to avoid listening for
keyboard and clickoutside event
- Re-implemented recently deleted table cell soft focus component logic
into RecordTableCellDisplayMode
- Created component selector isAtLeastOneTableRowSelectedSelector
- Drill down hotkey scope when opening a dropdown
- Improved debug logs
# Description
Closes [#696](https://github.com/twentyhq/core-team-issues/issues/696)
- `useAction` hooks have been removed for all actions
- Every action can now declare a react component
- Some standard action components have been introduced: `Action`,
`ActionLink` and `ActionModal`
- The `ActionDisplay` component uses the new `displayType` prop of the
`ActionMenuContext` to render the right component for the action
according to its container: `ActionButton`, `ActionDropdownItem` or
`ActionListItem`
- The `ActionDisplayer` wraps the action component inside a context
which gives it all the information about the action
-`actionMenuEntriesComponenState` has been removed and now all actions
are computed directly using `useRegisteredAction`
- This computation is done inside `ActionMenuContextProvider` and the
actions are passed inside a context
- `actionMenuType` gives information about the container of the action,
so the action can know wether or not to close this container upon
execution
#11414
Conditionally render MobileNavigationBar based on user authentication
status
- Added useIsLogged hook to check if the user is authenticated.
- Updated MobileNavigationBar component to render only when the user is
logged in.

---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR was originally about fixing advanced filter dropdown auto resize
to avoid breaking the app main container, but the regression is not
limited to advanced filter dropdown, so this PR fixes the regression for
every dropdown in the app.
This PR adds a max dropdown max width to allow resizing dropdowns
horizontally also, which can happen easily for the advanced filter
dropdown.
In this PR we also start removing `fieldMetadataItemUsedInDropdown` in
component `AdvancedFilterDropdownTextInput` because it has no impact
outside of this component which is used only once.
The autoresize behavior determines the right padding-bottom between
mobile and PC.
Mobile :
<img width="604" alt="Capture d’écran 2025-04-07 à 16 03 12"
src="https://github.com/user-attachments/assets/fbdd8020-1bfc-4e01-8a05-3a9f114cdd40"
/>
PC :
<img width="757" alt="Capture d’écran 2025-04-07 à 16 03 30"
src="https://github.com/user-attachments/assets/f80a5967-8f60-40bb-ae3c-fa9eb4c65707"
/>
Fixes https://github.com/twentyhq/core-team-issues/issues/725
Fixes https://github.com/twentyhq/twenty/issues/11409
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
After investiagting the different options ([see related
issue](https://github.com/twentyhq/core-team-issues/issues/660#issuecomment-2766030972))
I decided to add a "Verify Component" and a to build a custom Layout for
this route.
Reason I cannot use the default one is to have all preloaded once the
user changes website and lands on the verify route.
Reason I did not modify the DefaultLayout to match our need is that is
would require many changes in order to avoid preloading states for our
specific usecase.
Fixes https://github.com/twentyhq/core-team-issues/issues/660
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## What
- Deprecate overlayscrollbars as we decided to follow the native
behavior
- rework on performances (avoid calling recoil states too much at field
level which is quite expensive)
- Also implements:
https://github.com/twentyhq/core-team-issues/issues/569
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This PR fixes a bug that prevented to do the matching of an imported CSV
file that contains a SELECT type column.
Fixes https://github.com/twentyhq/twenty/issues/11220
## Stacking context improvement
During the development it was clear that we lacked a reliable way to
understand our own z indices for components like modal, portaled
dropdown, overlay background, etc.
So in this PR we introduce a new enum RootStackingContextZIndices, this
enum allows to keep track of our root stacking context component
z-index, and because it is an enum, it prevents any conflict.
See
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context
for reference.
## Component cleaning
Components have been reorganized in a SubMatchingSelectRow component
The Dropdown component has been used to replace the SelectInput
component which doesn't fit this use case because we are not in a cell,
we just need a simple standalone dropdown, though it would be
interesting to extract the UI part of the SelectInput, to share it here,
the benefit is not obvious since we already have good shared components
like Tag and Dropdown to implement this specific use case.
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/591
Same than for `twenty-shared` made in
https://github.com/twentyhq/twenty/pull/11083.
## TODO
- [x] Manual migrate twenty-website twenty-ui imports
## What's next:
- Generate barrel and migration script factorization within own package
+ tests
- Refactoring using preconstruct ? TimeBox
- Lint circular dependencies
- Lint import from barrel and forbid them
### Preconstruct
We need custom rollup plugins addition, but preconstruct does not expose
its rollup configuration. It might be possible to handle this using the
babel overrides. But was a big tunnel.
We could give it a try afterwards ! ( allowing cjs interop and stuff
like that )
Stuck to vite lib app
Closed related PRs:
- https://github.com/twentyhq/twenty/pull/11294
- https://github.com/twentyhq/twenty/pull/11203
This PR fixes the issue about the easy fast follow-up part on advanced
filter.
Fixes https://github.com/twentyhq/core-team-issues/issues/675
Changes :
- Changed horizontal gap to spacing(1) for AdvancedFilterDropdownRow
- Created a DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET for all
sub-dropdowns in advanced filter dropdown with a y-offset of 2px.
- Created a DropdownOffset type
- Used a padding-left of spacing(2.25) in
AdvancedFilterLogicalOperatorCell to allign the disabled text with the
text in the Select component
- Added IconTrash and accent danger on
AdvancedFilterRecordFilterGroupOptionsDropdown and
AdvancedFilterRecordFilterOptionsDropdown
- Removed unnecessary CSS properties on
AdvancedFilterRootRecordFilterGroup
- Set dropdownMenuWith to 280 for AdvancedFilterValueInputDropdownButton
- Fixed Dropdown generic clickable component container that was
expanding
- Set IconFilter instead of IconFilterCog in AdvancedFilterChip
- Set AdvancedFilterDropdownButton dropdown content width to 650 instead
of 800
- Refactored generic IconButton component so that it disambiguates
secondary and tertiary variant for the color CSS props