mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-30 20:27:55 +00:00 
			
		
		
		
	| @@ -102,6 +102,7 @@ export const DeleteRecordsActionEffect = ({ | |||||||
|         position, |         position, | ||||||
|         Icon: IconTrash, |         Icon: IconTrash, | ||||||
|         accent: 'danger', |         accent: 'danger', | ||||||
|  |         isPinned: true, | ||||||
|         onClick: () => { |         onClick: () => { | ||||||
|           setIsDeleteRecordsModalOpen(true); |           setIsDeleteRecordsModalOpen(true); | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import styled from '@emotion/styled'; | import styled from '@emotion/styled'; | ||||||
|  |  | ||||||
|  | import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton'; | ||||||
| import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry'; | import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry'; | ||||||
| import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; | import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector'; | ||||||
| import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; | ||||||
| @@ -30,7 +31,9 @@ export const RecordIndexActionMenuBar = () => { | |||||||
|     actionMenuEntriesComponentSelector, |     actionMenuEntriesComponentSelector, | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   if (actionMenuEntries.length === 0) { |   const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned); | ||||||
|  |  | ||||||
|  |   if (pinnedEntries.length === 0) { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -42,9 +45,10 @@ export const RecordIndexActionMenuBar = () => { | |||||||
|       }} |       }} | ||||||
|     > |     > | ||||||
|       <StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel> |       <StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel> | ||||||
|       {actionMenuEntries.map((entry, index) => ( |       {pinnedEntries.map((entry, index) => ( | ||||||
|         <RecordIndexActionMenuBarEntry key={index} entry={entry} /> |         <RecordIndexActionMenuBarEntry key={index} entry={entry} /> | ||||||
|       ))} |       ))} | ||||||
|  |       <RecordIndexActionMenuBarAllActionsButton /> | ||||||
|     </BottomBar> |     </BottomBar> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; | ||||||
|  | import { useTheme } from '@emotion/react'; | ||||||
|  | import styled from '@emotion/styled'; | ||||||
|  | import { IconLayoutSidebarRightExpand } from 'twenty-ui'; | ||||||
|  |  | ||||||
|  | const StyledButton = styled.div` | ||||||
|  |   border-radius: ${({ theme }) => theme.border.radius.sm}; | ||||||
|  |   color: ${({ theme }) => theme.font.color.secondary}; | ||||||
|  |   cursor: pointer; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |  | ||||||
|  |   padding: ${({ theme }) => theme.spacing(2)}; | ||||||
|  |   transition: background ${({ theme }) => theme.animation.duration.fast} ease; | ||||||
|  |   user-select: none; | ||||||
|  |  | ||||||
|  |   &:hover { | ||||||
|  |     background: ${({ theme }) => theme.background.tertiary}; | ||||||
|  |   } | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | const StyledButtonLabel = styled.div` | ||||||
|  |   font-weight: ${({ theme }) => theme.font.weight.medium}; | ||||||
|  |   margin-left: ${({ theme }) => theme.spacing(1)}; | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | const StyledShortcutLabel = styled.div` | ||||||
|  |   color: ${({ theme }) => theme.font.color.light}; | ||||||
|  |   font-weight: ${({ theme }) => theme.font.weight.medium}; | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | const StyledSeparator = styled.div<{ size: 'sm' | 'md' }>` | ||||||
|  |   background: ${({ theme }) => theme.border.color.light}; | ||||||
|  |   height: ${({ theme, size }) => theme.spacing(size === 'sm' ? 4 : 8)}; | ||||||
|  |   margin: 0 ${({ theme }) => theme.spacing(1)}; | ||||||
|  |   width: 1px; | ||||||
|  | `; | ||||||
|  |  | ||||||
|  | export const RecordIndexActionMenuBarAllActionsButton = () => { | ||||||
|  |   const theme = useTheme(); | ||||||
|  |   const { openCommandMenu } = useCommandMenu(); | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <StyledSeparator size="md" /> | ||||||
|  |       <StyledButton onClick={() => openCommandMenu()}> | ||||||
|  |         <IconLayoutSidebarRightExpand size={theme.icon.size.md} /> | ||||||
|  |         <StyledButtonLabel>All Actions</StyledButtonLabel> | ||||||
|  |         <StyledSeparator size="sm" /> | ||||||
|  |         <StyledShortcutLabel>⌘K</StyledShortcutLabel> | ||||||
|  |       </StyledButton> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @@ -2,31 +2,24 @@ import { useTheme } from '@emotion/react'; | |||||||
| import styled from '@emotion/styled'; | import styled from '@emotion/styled'; | ||||||
|  |  | ||||||
| import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; | import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry'; | ||||||
| import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent'; |  | ||||||
|  |  | ||||||
| type RecordIndexActionMenuBarEntryProps = { | type RecordIndexActionMenuBarEntryProps = { | ||||||
|   entry: ActionMenuEntry; |   entry: ActionMenuEntry; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const StyledButton = styled.div<{ accent: MenuItemAccent }>` | const StyledButton = styled.div` | ||||||
|   border-radius: ${({ theme }) => theme.border.radius.sm}; |   border-radius: ${({ theme }) => theme.border.radius.sm}; | ||||||
|   color: ${(props) => |   color: ${({ theme }) => theme.font.color.secondary}; | ||||||
|     props.accent === 'danger' |  | ||||||
|       ? props.theme.color.red |  | ||||||
|       : props.theme.font.color.secondary}; |  | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
|   display: flex; |   display: flex; | ||||||
|   justify-content: center; |   justify-content: center; | ||||||
|  |  | ||||||
|   padding: ${({ theme }) => theme.spacing(2)}; |   padding: ${({ theme }) => theme.spacing(2)}; | ||||||
|   transition: background 0.1s ease; |   transition: background ${({ theme }) => theme.animation.duration.fast} ease; | ||||||
|   user-select: none; |   user-select: none; | ||||||
|  |  | ||||||
|   &:hover { |   &:hover { | ||||||
|     background: ${({ theme, accent }) => |     background: ${({ theme }) => theme.background.tertiary}; | ||||||
|       accent === 'danger' |  | ||||||
|         ? theme.background.danger |  | ||||||
|         : theme.background.tertiary}; |  | ||||||
|   } |   } | ||||||
| `; | `; | ||||||
|  |  | ||||||
| @@ -40,10 +33,7 @@ export const RecordIndexActionMenuBarEntry = ({ | |||||||
| }: RecordIndexActionMenuBarEntryProps) => { | }: RecordIndexActionMenuBarEntryProps) => { | ||||||
|   const theme = useTheme(); |   const theme = useTheme(); | ||||||
|   return ( |   return ( | ||||||
|     <StyledButton |     <StyledButton onClick={() => entry.onClick?.()}> | ||||||
|       accent={entry.accent ?? 'default'} |  | ||||||
|       onClick={() => entry.onClick?.()} |  | ||||||
|     > |  | ||||||
|       {entry.Icon && <entry.Icon size={theme.icon.size.md} />} |       {entry.Icon && <entry.Icon size={theme.icon.size.md} />} | ||||||
|       <StyledButtonLabel>{entry.label}</StyledButtonLabel> |       <StyledButtonLabel>{entry.label}</StyledButtonLabel> | ||||||
|     </StyledButton> |     </StyledButton> | ||||||
|   | |||||||
| @@ -10,15 +10,15 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto | |||||||
| import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; | import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; | ||||||
| import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; | import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState'; | ||||||
| import { userEvent, waitFor, within } from '@storybook/test'; | import { userEvent, waitFor, within } from '@storybook/test'; | ||||||
| import { IconCheckbox, IconTrash } from 'twenty-ui'; | import { IconTrash, RouterDecorator } from 'twenty-ui'; | ||||||
|  |  | ||||||
| const deleteMock = jest.fn(); | const deleteMock = jest.fn(); | ||||||
| const markAsDoneMock = jest.fn(); |  | ||||||
|  |  | ||||||
| const meta: Meta<typeof RecordIndexActionMenuBar> = { | const meta: Meta<typeof RecordIndexActionMenuBar> = { | ||||||
|   title: 'Modules/ActionMenu/RecordIndexActionMenuBar', |   title: 'Modules/ActionMenu/RecordIndexActionMenuBar', | ||||||
|   component: RecordIndexActionMenuBar, |   component: RecordIndexActionMenuBar, | ||||||
|   decorators: [ |   decorators: [ | ||||||
|  |     RouterDecorator, | ||||||
|     (Story) => ( |     (Story) => ( | ||||||
|       <ContextStoreComponentInstanceContext.Provider |       <ContextStoreComponentInstanceContext.Provider | ||||||
|         value={{ instanceId: 'story-action-menu' }} |         value={{ instanceId: 'story-action-menu' }} | ||||||
| @@ -48,6 +48,7 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = { | |||||||
|                 [ |                 [ | ||||||
|                   'delete', |                   'delete', | ||||||
|                   { |                   { | ||||||
|  |                     isPinned: true, | ||||||
|                     key: 'delete', |                     key: 'delete', | ||||||
|                     label: 'Delete', |                     label: 'Delete', | ||||||
|                     position: 0, |                     position: 0, | ||||||
| @@ -55,16 +56,6 @@ const meta: Meta<typeof RecordIndexActionMenuBar> = { | |||||||
|                     onClick: deleteMock, |                     onClick: deleteMock, | ||||||
|                   }, |                   }, | ||||||
|                 ], |                 ], | ||||||
|                 [ |  | ||||||
|                   'markAsDone', |  | ||||||
|                   { |  | ||||||
|                     key: 'markAsDone', |  | ||||||
|                     label: 'Mark as done', |  | ||||||
|                     position: 1, |  | ||||||
|                     Icon: IconCheckbox, |  | ||||||
|                     onClick: markAsDoneMock, |  | ||||||
|                   }, |  | ||||||
|                 ], |  | ||||||
|               ]), |               ]), | ||||||
|             ); |             ); | ||||||
|             set( |             set( | ||||||
| @@ -120,12 +111,8 @@ export const WithButtonClicks: Story = { | |||||||
|     const deleteButton = await canvas.findByText('Delete'); |     const deleteButton = await canvas.findByText('Delete'); | ||||||
|     await userEvent.click(deleteButton); |     await userEvent.click(deleteButton); | ||||||
|  |  | ||||||
|     const markAsDoneButton = await canvas.findByText('Mark as done'); |  | ||||||
|     await userEvent.click(markAsDoneButton); |  | ||||||
|  |  | ||||||
|     await waitFor(() => { |     await waitFor(() => { | ||||||
|       expect(deleteMock).toHaveBeenCalled(); |       expect(deleteMock).toHaveBeenCalled(); | ||||||
|       expect(markAsDoneMock).toHaveBeenCalled(); |  | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ export type ActionMenuEntry = { | |||||||
|   label: string; |   label: string; | ||||||
|   position: number; |   position: number; | ||||||
|   Icon: IconComponent; |   Icon: IconComponent; | ||||||
|  |   isPinned?: boolean; | ||||||
|   accent?: MenuItemAccent; |   accent?: MenuItemAccent; | ||||||
|   onClick?: (event?: MouseEvent<HTMLElement>) => void; |   onClick?: (event?: MouseEvent<HTMLElement>) => void; | ||||||
|   ConfirmationModal?: ReactNode; |   ConfirmationModal?: ReactNode; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Raphaël Bosi
					Raphaël Bosi