From 28ca9a9e498b1524363463d2235144dba7367f3c Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Wed, 6 Sep 2023 16:41:26 +0200 Subject: [PATCH] Refactor/new menu item (#1448) * wip * finished * Added disabled * Fixed disabled * Finished cleaning * Minor fixes from merge * Added docs * Added PascalCase * Fix from review * Fixes from merge * Fix lint * Fixed storybook tests --- .../developer/frontend/best-practices.mdx | 29 +++++++ front/package.json | 1 + .../activities/components/ActivityEditor.tsx | 2 +- .../ActivityAssigneeEditableField.tsx | 2 +- .../ActivityRelationEditableField.tsx | 2 +- .../companies/components/CompanyBoardCard.tsx | 2 +- .../components/CompanyProgressPicker.tsx | 16 ++-- .../companiesAvailableColumnDefinitions.tsx | 20 ++--- .../useCompanyTableContextMenuEntries.tsx | 6 +- .../hooks/useUpdateCompanyBoardColumns.ts | 23 +++-- .../companies/utils/fieldsForCompany.tsx | 16 ++-- .../modules/people/components/PeopleCard.tsx | 28 +++--- .../peopleAvailableColumnDefinitions.tsx | 18 ++-- .../usePeopleTableContextMenuEntries.tsx | 6 +- .../modules/people/utils/fieldsForPerson.tsx | 19 ++-- .../pipelineAvailableFieldDefinitions.tsx | 8 +- .../pipeline/hooks/usePipelineStages.ts | 2 +- .../components/MatchColumnSelect.tsx | 85 ++++-------------- .../components/TemplateColumn.tsx | 2 +- .../ValidationStep/components/columns.tsx | 2 +- .../spreadsheet-import/tests/mockRsiValues.ts | 4 +- .../modules/spreadsheet-import/types/index.ts | 5 +- .../ui/board/components/BoardColumn.tsx | 7 +- .../components/BoardColumnEditTitleMenu.tsx | 32 +++---- .../ui/board/components/BoardColumnMenu.tsx | 36 ++++---- .../BoardOptionsDropdownContent.tsx | 43 +++++---- .../BoardColumnEditTitleMenu.stories.tsx | 4 +- .../hooks/useBoardContextMenuEntries.tsx | 2 +- .../ui/board/types/BoardColumnDefinition.ts | 4 +- .../components/ContextMenuEntry.tsx | 24 ++--- .../components/DropdownMenuCheckableItem.tsx | 52 ----------- .../components/DropdownMenuHeader.tsx | 27 ++++-- .../dropdown/components/DropdownMenuItem.tsx | 79 ----------------- .../components/DropdownMenuSelectableItem.tsx | 87 ------------------- .../StyledDropdownMenuItemsContainer.tsx | 1 + .../__stories__/DropdownMenu.stories.tsx | 87 +++++++++---------- .../components/EditableField.tsx | 11 ++- .../GenericEditableBooleanField.tsx | 2 +- .../components/GenericEditableDateField.tsx | 2 +- .../components/GenericEditableNumberField.tsx | 2 +- .../components/GenericEditablePhoneField.tsx | 2 +- .../GenericEditableRelationField.tsx | 2 +- .../components/GenericEditableTextField.tsx | 2 +- .../components/GenericEditableURLField.tsx | 2 +- .../components/ProbabilityEditableField.tsx | 2 +- .../editable-field/types/FieldDefinition.ts | 4 +- .../ui/editable-field/types/ViewField.ts | 3 +- .../variants/components/DateEditableField.tsx | 7 +- .../components/PhoneEditableField.tsx | 7 +- .../__stories__/DateEditableField.stories.tsx | 6 +- .../PhoneEditableField.stories.tsx | 6 +- .../components/FilterDropdownFilterSelect.tsx | 12 +-- .../FilterDropdownOperandButton.tsx | 5 +- .../FilterDropdownOperandSelect.tsx | 9 +- .../components/SortAndFilterBar.tsx | 16 ++-- .../components/SortDropdownButton.tsx | 25 +++--- .../components/SortOrFilterChip.tsx | 18 ++-- .../filter-n-sort/types/FilterDefinition.ts | 4 +- .../ui/filter-n-sort/types/interface.ts | 5 +- .../modules/ui/icon/types/IconComponent.ts | 4 +- .../components/MultipleEntitySelect.tsx | 35 ++++---- .../components/SingleEntitySelect.tsx | 10 +-- .../components/SingleEntitySelectBase.tsx | 57 ++++++------ .../components/ShowPageAddButton.tsx | 26 +++--- .../ui/menu-item/components/MenuItem.tsx | 22 +++-- .../components/MenuItemMultiSelectAvatar.tsx | 51 +++++++++++ .../menu-item/components/MenuItemNavigate.tsx | 2 +- .../menu-item/components/MenuItemSelect.tsx | 33 ++++++- .../components/MenuItemSelectAvatar.tsx | 55 ++++++++++++ .../components/MenuItemSelectColor.tsx | 59 +++++++++++++ .../components/MenuItemLeftContent.tsx | 7 +- .../components/StyledMenuItemBase.tsx | 11 ++- .../ui/menu-item/types/MenuItemAccent.ts | 2 +- .../ui/table/components/ColumnHead.tsx | 13 ++- .../components/EntityTableColumnMenu.tsx | 30 +++---- .../ui/table/components/EntityTableHeader.tsx | 2 +- .../TableOptionsDropdownContent.tsx | 72 ++++----------- .../TableOptionsDropdownSection.tsx | 40 ++++----- .../components/TableUpdateViewButtonGroup.tsx | 14 ++- .../components/TableViewsDropdownButton.tsx | 47 +++++----- front/src/modules/ui/tag/components/Tag.tsx | 4 +- .../src/modules/ui/theme/constants/colors.ts | 39 ++++++--- .../ui/theme/utils/castStringAsThemeColor.ts | 7 ++ .../Companies.filterBy.stories.tsx | 24 ++--- .../__stories__/Companies.sortBy.stories.tsx | 5 +- .../src/pages/companies/companies-filters.tsx | 15 ++-- front/src/pages/companies/companies-sorts.tsx | 10 +-- .../constants/companyShowFieldDefinition.tsx | 14 +-- .../opportunities/opportunities-filters.tsx | 11 +-- .../opportunities/opportunities-sorts.tsx | 6 +- .../__stories__/People.filterBy.stories.tsx | 14 +-- .../__stories__/People.sortBy.stories.tsx | 8 +- .../constants/personShowFieldDefinition.tsx | 16 ++-- front/src/pages/people/people-filters.tsx | 17 ++-- front/src/pages/people/people-sorts.tsx | 13 ++- front/src/pages/tasks/tasks-filters.tsx | 2 +- 96 files changed, 816 insertions(+), 918 deletions(-) delete mode 100644 front/src/modules/ui/dropdown/components/DropdownMenuCheckableItem.tsx delete mode 100644 front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx delete mode 100644 front/src/modules/ui/dropdown/components/DropdownMenuSelectableItem.tsx create mode 100644 front/src/modules/ui/menu-item/components/MenuItemMultiSelectAvatar.tsx create mode 100644 front/src/modules/ui/menu-item/components/MenuItemSelectAvatar.tsx create mode 100644 front/src/modules/ui/menu-item/components/MenuItemSelectColor.tsx create mode 100644 front/src/modules/ui/theme/utils/castStringAsThemeColor.ts diff --git a/docs/docs/developer/frontend/best-practices.mdx b/docs/docs/developer/frontend/best-practices.mdx index 3e1d2cf814..c31df21dd3 100644 --- a/docs/docs/developer/frontend/best-practices.mdx +++ b/docs/docs/developer/frontend/best-practices.mdx @@ -236,6 +236,35 @@ function Form() { } ``` +## Component as props + +Try as much as possible to pass uninstanciated components as props, so chilren can decide on their own of what props they need to pass. + +The most common example for that is icon components : + +```tsx +function SomeParentComponent() { + return ( + + ) +} + +// In MyComponent +function MyComponent({ MyIcon }: { MyIcon: IconComponent }) { + const theme = useTheme(); + + return ( +
+ +
+ ) +} +``` + +For React to understand that the component is a component, you need to use PascalCase, to later instanciate it with `` + +```tsx + ## Prop Drilling: Keep It Minimal Prop drilling, in the React context, refers to the practice of passing state variables and their setters through multiple component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to: diff --git a/front/package.json b/front/package.json index d1372bacb6..2fe5df664e 100644 --- a/front/package.json +++ b/front/package.json @@ -71,6 +71,7 @@ "lint": "eslint src --max-warnings=0", "storybook:dev": "storybook dev -p 6006 -s ../public", "storybook:test": "test-storybook", + "storybook:test-slow": "test-storybook --maxWorkers=3", "storybook:build": "storybook build -s public", "storybook:coverage": "test-storybook --coverage --maxWorkers=3 && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage", "graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js", diff --git a/front/src/modules/activities/components/ActivityEditor.tsx b/front/src/modules/activities/components/ActivityEditor.tsx index 5d01a62653..826893f8c6 100644 --- a/front/src/modules/activities/components/ActivityEditor.tsx +++ b/front/src/modules/activities/components/ActivityEditor.tsx @@ -185,7 +185,7 @@ export function ActivityEditor({ <> } + Icon={IconCalendar} label="Due date" onSubmit={(newDate) => { updateActivityMutation({ diff --git a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx index a5d9f7e007..61ae4cb32a 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityAssigneeEditableField.tsx @@ -23,7 +23,7 @@ export function ActivityAssigneeEditableField({ activity }: OwnProps) { scope: RelationPickerHotkeyScope.RelationPicker, }} label="Assignee" - iconLabel={} + IconLabel={IconUserCircle} editModeContent={ } diff --git a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx index 8a55265114..a096d20e31 100644 --- a/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx +++ b/front/src/modules/activities/editable-fields/components/ActivityRelationEditableField.tsx @@ -28,7 +28,7 @@ export function ActivityRelationEditableField({ activity }: OwnProps) { customEditHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker, }} - iconLabel={} + IconLabel={IconArrowUpRight} editModeContent={ } diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index d75dab85ae..77d2d9d03d 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -167,7 +167,7 @@ export function CompanyBoardCard() { value={{ key: viewField.key, name: viewField.name, - icon: viewField.icon, + Icon: viewField.Icon, type: viewField.metadata.type, metadata: viewField.metadata, }} diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/CompanyProgressPicker.tsx index 26ddecb0d2..4ff2c92fc9 100644 --- a/front/src/modules/companies/components/CompanyProgressPicker.tsx +++ b/front/src/modules/companies/components/CompanyProgressPicker.tsx @@ -1,11 +1,9 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { useTheme } from '@emotion/react'; import { useRecoilState } from 'recoil'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; @@ -13,6 +11,7 @@ import { IconChevronDown } from '@/ui/icon'; import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery'; @@ -47,8 +46,6 @@ export function CompanyProgressPicker({ string | null >(null); - const theme = useTheme(); - const [currentPipeline] = useRecoilState(currentPipelineState); const currentPipelineStages = useMemo( @@ -89,22 +86,21 @@ export function CompanyProgressPicker({ {isProgressSelectionUnfolded ? ( {currentPipelineStages.map((pipelineStage, index) => ( - { handlePipelineStageChange(pipelineStage.id); }} - > - {pipelineStage.name} - + text={pipelineStage.name} + /> ))} ) : ( <> } + EndIcon={IconChevronDown} onClick={() => setIsProgressSelectionUnfolded(true)} > {selectedPipelineStage?.name} diff --git a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx index 6ea68721a4..362d33af73 100644 --- a/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx +++ b/front/src/modules/companies/constants/companiesAvailableColumnDefinitions.tsx @@ -29,7 +29,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBuildingSkyscraper, size: 180, index: 0, metadata: { @@ -43,7 +43,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconLink, size: 100, index: 1, metadata: { @@ -56,7 +56,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconUserCircle, size: 150, index: 2, metadata: { @@ -69,7 +69,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconCalendarEvent, size: 150, index: 3, metadata: { @@ -81,7 +81,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconUsers, size: 150, index: 4, metadata: { @@ -94,7 +94,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBrandLinkedin, size: 170, index: 5, metadata: { @@ -107,7 +107,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconMap, size: 170, index: 6, metadata: { @@ -120,7 +120,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconTarget, size: 150, index: 7, metadata: { @@ -132,7 +132,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconMoneybag, size: 150, index: 8, metadata: { @@ -143,7 +143,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBrandX, size: 150, index: 9, metadata: { diff --git a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx index 5cecff8273..7dbf90f288 100644 --- a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx +++ b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx @@ -26,19 +26,19 @@ export function useCompanyTableContextMenuEntries() { setContextMenuEntries([ } + Icon={IconNotes} onClick={() => handleButtonClick(ActivityType.Note)} key="note" />, } + Icon={IconCheckbox} onClick={() => handleButtonClick(ActivityType.Task)} key="task" />, } + Icon={IconTrash} accent="danger" onClick={() => deleteSelectedCompanies()} key="delete" diff --git a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts index 5ec6348dfc..85002c8fb7 100644 --- a/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts +++ b/front/src/modules/companies/hooks/useUpdateCompanyBoardColumns.ts @@ -5,6 +5,7 @@ import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardId import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition'; import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState'; +import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor'; import { Pipeline } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -97,12 +98,22 @@ export function useUpdateCompanyBoard() { }); const newBoardColumns: BoardColumnDefinition[] = - orderedPipelineStages?.map((pipelineStage) => ({ - id: pipelineStage.id, - title: pipelineStage.name, - colorCode: pipelineStage.color, - index: pipelineStage.index ?? 0, - })); + orderedPipelineStages?.map((pipelineStage) => { + if (!isThemeColor(pipelineStage.color)) { + console.warn( + `Color ${pipelineStage.color} is not recognized in useUpdateCompanyBoard.`, + ); + } + + return { + id: pipelineStage.id, + title: pipelineStage.name, + colorCode: isThemeColor(pipelineStage.color) + ? pipelineStage.color + : undefined, + index: pipelineStage.index ?? 0, + }; + }); if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) { set(boardColumnsState, newBoardColumns); diff --git a/front/src/modules/companies/utils/fieldsForCompany.tsx b/front/src/modules/companies/utils/fieldsForCompany.tsx index 991d94e82f..2287a09186 100644 --- a/front/src/modules/companies/utils/fieldsForCompany.tsx +++ b/front/src/modules/companies/utils/fieldsForCompany.tsx @@ -11,7 +11,7 @@ import { export const fieldsForCompany = [ { - icon: , + icon: IconBuildingSkyscraper, label: 'Name', key: 'name', alternateMatches: ['name', 'company name', 'company'], @@ -21,7 +21,7 @@ export const fieldsForCompany = [ example: 'Tim', }, { - icon: , + icon: IconMail, label: 'Domain name', key: 'domainName', alternateMatches: ['domain', 'domain name'], @@ -31,7 +31,7 @@ export const fieldsForCompany = [ example: 'apple.dev', }, { - icon: , + icon: IconBrandLinkedin, label: 'Linkedin URL', key: 'linkedinUrl', alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'], @@ -41,7 +41,7 @@ export const fieldsForCompany = [ example: 'https://www.linkedin.com/in/apple', }, { - icon: , + icon: IconMoneybag, label: 'ARR', key: 'annualRecurringRevenue', alternateMatches: [ @@ -64,7 +64,7 @@ export const fieldsForCompany = [ example: '1000000', }, { - icon: , + icon: IconTarget, label: 'ICP', key: 'idealCustomerProfile', alternateMatches: [ @@ -86,7 +86,7 @@ export const fieldsForCompany = [ example: 'true/false', }, { - icon: , + icon: IconBrandX, label: 'x URL', key: 'xUrl', alternateMatches: ['x', 'twitter', 'twitter url', 'x url'], @@ -96,7 +96,7 @@ export const fieldsForCompany = [ example: 'https://x.com/tim_cook', }, { - icon: , + icon: IconMap, label: 'Address', key: 'address', fieldType: { @@ -105,7 +105,7 @@ export const fieldsForCompany = [ example: 'Maple street', }, { - icon: , + icon: IconUsers, label: 'Employees', key: 'employees', alternateMatches: ['employees', 'total employees', 'number of employees'], diff --git a/front/src/modules/people/components/PeopleCard.tsx b/front/src/modules/people/components/PeopleCard.tsx index ac053e7e81..fbb9e36ca7 100644 --- a/front/src/modules/people/components/PeopleCard.tsx +++ b/front/src/modules/people/components/PeopleCard.tsx @@ -1,15 +1,14 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { getOperationName } from '@apollo/client/utilities'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { IconDotsVertical, IconLinkOff, IconTrash } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; import { @@ -72,10 +71,6 @@ const StyledJobTitle = styled.div` } `; -const StyledRemoveOption = styled.div` - color: ${({ theme }) => theme.color.red}; -`; - export function PeopleCard({ person, hasBottomBorder = true, @@ -93,8 +88,6 @@ export function PeopleCard({ placement: 'right-start', }); - const theme = useTheme(); - useListenClickOutside({ refs: [refs.floating], callback: () => { @@ -175,14 +168,17 @@ export function PeopleCard({ e.stopPropagation()} > - - - Detach relation - - - - Delete person - + + )} diff --git a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx index 8861b7a4c5..b98fa47eef 100644 --- a/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx +++ b/front/src/modules/people/constants/peopleAvailableColumnDefinitions.tsx @@ -27,7 +27,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconUser, size: 210, index: 0, metadata: { @@ -43,7 +43,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconMail, size: 150, index: 1, metadata: { @@ -55,7 +55,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBuildingSkyscraper, size: 150, index: 2, metadata: { @@ -67,7 +67,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconPhone, size: 150, index: 3, metadata: { @@ -79,7 +79,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconCalendarEvent, size: 150, index: 4, metadata: { @@ -90,7 +90,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconMap, size: 150, index: 5, metadata: { @@ -102,7 +102,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBriefcase, size: 150, index: 6, metadata: { @@ -114,7 +114,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBrandLinkedin, size: 150, index: 7, metadata: { @@ -126,7 +126,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition, + Icon: IconBrandX, size: 150, index: 8, metadata: { diff --git a/front/src/modules/people/hooks/usePeopleTableContextMenuEntries.tsx b/front/src/modules/people/hooks/usePeopleTableContextMenuEntries.tsx index 8862d552f2..b7341f86e1 100644 --- a/front/src/modules/people/hooks/usePeopleTableContextMenuEntries.tsx +++ b/front/src/modules/people/hooks/usePeopleTableContextMenuEntries.tsx @@ -60,19 +60,19 @@ export function usePersonTableContextMenuEntries() { setContextMenuEntries([ } + Icon={IconNotes} onClick={() => handleActivityClick(ActivityType.Note)} key="note" />, } + Icon={IconCheckbox} onClick={() => handleActivityClick(ActivityType.Task)} key="task" />, } + Icon={IconTrash} accent="danger" onClick={handleDeleteClick} key="delete" diff --git a/front/src/modules/people/utils/fieldsForPerson.tsx b/front/src/modules/people/utils/fieldsForPerson.tsx index 2b16319574..53f0aec884 100644 --- a/front/src/modules/people/utils/fieldsForPerson.tsx +++ b/front/src/modules/people/utils/fieldsForPerson.tsx @@ -1,5 +1,6 @@ import { isValidPhoneNumber } from 'libphonenumber-js'; +import { Fields } from '@/spreadsheet-import/types'; import { IconBrandLinkedin, IconBrandX, @@ -11,7 +12,7 @@ import { export const fieldsForPerson = [ { - icon: , + icon: IconUser, label: 'Firstname', key: 'firstName', alternateMatches: ['first name', 'first', 'firstname'], @@ -21,7 +22,7 @@ export const fieldsForPerson = [ example: 'Tim', }, { - icon: , + icon: IconUser, label: 'Lastname', key: 'lastName', alternateMatches: ['last name', 'last', 'lastname'], @@ -31,7 +32,7 @@ export const fieldsForPerson = [ example: 'Cook', }, { - icon: , + icon: IconMail, label: 'Email', key: 'email', alternateMatches: ['email', 'mail'], @@ -41,7 +42,7 @@ export const fieldsForPerson = [ example: 'tim@apple.dev', }, { - icon: , + icon: IconBrandLinkedin, label: 'Linkedin URL', key: 'linkedinUrl', alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'], @@ -51,7 +52,7 @@ export const fieldsForPerson = [ example: 'https://www.linkedin.com/in/timcook', }, { - icon: , + icon: IconBrandX, label: 'X URL', key: 'xUrl', alternateMatches: ['x', 'x url'], @@ -61,7 +62,7 @@ export const fieldsForPerson = [ example: 'https://x.com/tim_cook', }, { - icon: , + icon: IconBriefcase, label: 'Job title', key: 'jobTitle', alternateMatches: ['job', 'job title'], @@ -71,7 +72,7 @@ export const fieldsForPerson = [ example: 'CEO', }, { - icon: , + icon: IconBriefcase, label: 'Phone', key: 'phone', fieldType: { @@ -88,7 +89,7 @@ export const fieldsForPerson = [ ], }, { - icon: , + icon: IconMap, label: 'City', key: 'city', fieldType: { @@ -96,4 +97,4 @@ export const fieldsForPerson = [ }, example: 'Seattle', }, -] as const; +] as Fields; diff --git a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx index 20f87db134..0e1cb67c47 100644 --- a/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx +++ b/front/src/modules/pipeline/constants/pipelineAvailableFieldDefinitions.tsx @@ -19,7 +19,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition, + Icon: IconCalendarEvent, metadata: { type: 'date', fieldName: 'closeDate', @@ -29,7 +29,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition, + Icon: IconCurrencyDollar, metadata: { type: 'number', fieldName: 'amount', @@ -39,7 +39,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition, + Icon: IconProgressCheck, metadata: { type: 'probability', fieldName: 'probability', @@ -49,7 +49,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition, + Icon: IconUser, metadata: { type: 'relation', fieldName: 'pointOfContact', diff --git a/front/src/modules/pipeline/hooks/usePipelineStages.ts b/front/src/modules/pipeline/hooks/usePipelineStages.ts index db73c77179..81b73a0246 100644 --- a/front/src/modules/pipeline/hooks/usePipelineStages.ts +++ b/front/src/modules/pipeline/hooks/usePipelineStages.ts @@ -22,7 +22,7 @@ export const usePipelineStages = () => { return createPipelineStageMutation({ variables: { data: { - color: boardColumn.colorCode, + color: boardColumn.colorCode ?? 'gray', id: boardColumn.id, index: boardColumn.index, name: boardColumn.title, diff --git a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx index 48a16a58f8..2d5365f76e 100644 --- a/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx +++ b/front/src/modules/spreadsheet-import/components/MatchColumnSelect.tsx @@ -14,44 +14,15 @@ import { ReadonlyDeep } from 'type-fest'; import type { SelectOption } from '@/spreadsheet-import/types'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; -import { IconChevronDown, TablerIconsProps } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect'; import { AppTooltip } from '@/ui/tooltip/AppTooltip'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; -const StyledDropdownItem = styled.div` - align-items: center; - background-color: ${({ theme }) => theme.background.tertiary}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - box-sizing: border-box; - display: flex; - flex-direction: row; - height: 32px; - padding-left: ${({ theme }) => theme.spacing(2)}; - padding-right: ${({ theme }) => theme.spacing(2)}; - width: 100%; - - &:hover { - background-color: ${({ theme }) => theme.background.quaternary}; - } -`; - -const StyledDropdownLabel = styled.span<{ isPlaceholder: boolean }>` - color: ${({ theme, isPlaceholder }) => - isPlaceholder ? theme.font.color.tertiary : theme.font.color.primary}; - display: flex; - flex: 1; - font-size: ${({ theme }) => theme.font.size.sm}; - font-weight: ${({ theme }) => theme.font.weight.regular}; - padding-left: ${({ theme }) => theme.spacing(1)}; - padding-right: ${({ theme }) => theme.spacing(1)}; -`; - const StyledFloatingDropdown = styled.div` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; @@ -69,11 +40,9 @@ export const MatchColumnSelect = ({ value, options: initialOptions, placeholder, - name, }: Props) => { const theme = useTheme(); - const dropdownItemRef = useRef(null); const dropdownContainerRef = useRef(null); const [isOpen, setIsOpen] = useState(false); @@ -123,16 +92,6 @@ export const MatchColumnSelect = ({ setIsOpen(false); } - function renderIcon(icon: ReadonlyDeep) { - if (icon && React.isValidElement(icon)) { - return React.cloneElement(icon as any, { - size: 16, - color: theme.font.color.primary, - }); - } - return null; - } - useListenClickOutside({ refs: [dropdownContainerRef], callback: () => { @@ -146,28 +105,20 @@ export const MatchColumnSelect = ({ return ( <> - { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - dropdownItemRef.current = node; - refs.setReference(node); - }} - onClick={handleDropdownItemClick} - > - {renderIcon(value?.icon)} - - {value?.label ?? placeholder} - - - +
+ +
{isOpen && createPortal( {options?.map((option) => ( <> - handleChange(option)} disabled={ option.disabled && value?.value !== option.value } - > - {renderIcon(option?.icon)} - {option.label} - + LeftIcon={option?.icon} + text={option.label} + /> {option.disabled && value?.value !== option.value && createPortal( @@ -204,9 +153,7 @@ export const MatchColumnSelect = ({ )} ))} - {options?.length === 0 && ( - No result - )} + {options?.length === 0 && } , diff --git a/front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx b/front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx index 20181e4692..8c64b995bc 100644 --- a/front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx +++ b/front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn.tsx @@ -109,7 +109,7 @@ export const TemplateColumn = ({ }); const selectOptions = [ { - icon: , + icon: IconForbid, value: 'do-not-import', label: 'Do not import', }, diff --git a/front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx b/front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx index b480d6fb39..8f11831eb9 100644 --- a/front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx +++ b/front/src/modules/spreadsheet-import/steps/components/ValidationStep/components/columns.tsx @@ -134,7 +134,7 @@ export const generateColumns = ( value={ value ? ({ - icon: null, + icon: undefined, ...value, } as const) : value diff --git a/front/src/modules/spreadsheet-import/tests/mockRsiValues.ts b/front/src/modules/spreadsheet-import/tests/mockRsiValues.ts index 6121e0bde1..a251903d25 100644 --- a/front/src/modules/spreadsheet-import/tests/mockRsiValues.ts +++ b/front/src/modules/spreadsheet-import/tests/mockRsiValues.ts @@ -1,5 +1,5 @@ import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport'; -import type { SpreadsheetOptions } from '@/spreadsheet-import/types'; +import type { Fields, SpreadsheetOptions } from '@/spreadsheet-import/types'; const fields = [ { @@ -85,7 +85,7 @@ const fields = [ }, example: 'true', }, -] as const; +] as Fields; const mockComponentBehaviourForTypes = ( props: SpreadsheetOptions, diff --git a/front/src/modules/spreadsheet-import/types/index.ts b/front/src/modules/spreadsheet-import/types/index.ts index 5379384f55..ffacca7c2e 100644 --- a/front/src/modules/spreadsheet-import/types/index.ts +++ b/front/src/modules/spreadsheet-import/types/index.ts @@ -3,6 +3,7 @@ import { ReadonlyDeep } from 'type-fest'; import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep'; import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow'; import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; export type SpreadsheetOptions = { // Is modal visible. @@ -65,7 +66,7 @@ export type Fields = ReadonlyDeep[]>; export type Field = { // Icon - icon: React.ReactNode; + icon: IconComponent | null | undefined; // UI-facing field label label: string; // Field's unique identifier @@ -96,7 +97,7 @@ export type Select = { export type SelectOption = { // Icon - icon?: React.ReactNode; + icon?: IconComponent | null; // UI-facing option label label: string; // Field entry matching criteria as well as select output diff --git a/front/src/modules/ui/board/components/BoardColumn.tsx b/front/src/modules/ui/board/components/BoardColumn.tsx index 0e6f2cb9bc..bedf04b654 100644 --- a/front/src/modules/ui/board/components/BoardColumn.tsx +++ b/front/src/modules/ui/board/components/BoardColumn.tsx @@ -2,6 +2,7 @@ import React from 'react'; import styled from '@emotion/styled'; import { Tag } from '@/ui/tag/components/Tag'; +import { ThemeColor } from '@/ui/theme/constants/colors'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; @@ -52,7 +53,7 @@ const StyledNumChildren = styled.div` `; export type BoardColumnProps = { - color: string; + color?: ThemeColor; title: string; onDelete?: (id: string) => void; onTitleEdit: (title: string, color: string) => void; @@ -97,7 +98,7 @@ export function BoardColumn({ return ( - + {!!totalAmount && ${totalAmount}} {numChildren} @@ -107,7 +108,7 @@ export function BoardColumn({ onDelete={onDelete} onTitleEdit={onTitleEdit} title={title} - color={color} + color={color ?? 'gray'} stageId={stageId} /> )} diff --git a/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx b/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx index 7a67f37b53..860234d902 100644 --- a/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnEditTitleMenu.tsx @@ -1,9 +1,10 @@ import { ChangeEvent, useState } from 'react'; import styled from '@emotion/styled'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; +import { MenuItemSelectColor } from '@/ui/menu-item/components/MenuItemSelectColor'; +import { ThemeColor } from '@/ui/theme/constants/colors'; import { textInputStyle } from '@/ui/theme/constants/effects'; import { debounce } from '~/utils/debounce'; @@ -32,21 +33,15 @@ export type BoardColumnEditTitleMenuProps = { onClose: () => void; title: string; onTitleEdit: (title: string, color: string) => void; - color: string; + color: ThemeColor; }; -const StyledColorSample = styled.div<{ colorName: string }>` - background-color: ${({ theme, colorName }) => - theme.tag.background[colorName]}; - border: 1px solid - ${({ theme, colorName }) => - theme.color[colorName as keyof typeof theme.color]}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - height: 12px; - width: 12px; -`; +type ColumnColorOption = { + name: string; + id: ThemeColor; +}; -export const COLOR_OPTIONS = [ +export const COLUMN_COLOR_OPTIONS: ColumnColorOption[] = [ { name: 'Green', id: 'green' }, { name: 'Turquoise', id: 'turquoise' }, { name: 'Sky', id: 'sky' }, @@ -85,18 +80,17 @@ export function BoardColumnEditTitleMenu({ /> - {COLOR_OPTIONS.map((colorOption) => ( - ( + { onTitleEdit(title, colorOption.id); onClose(); }} + color={colorOption.id} selected={colorOption.id === color} - > - - {colorOption.name} - + text={colorOption.name} + /> ))} ); diff --git a/front/src/modules/ui/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/board/components/BoardColumnMenu.tsx index 6c23866b0f..4ffcac1327 100644 --- a/front/src/modules/ui/board/components/BoardColumnMenu.tsx +++ b/front/src/modules/ui/board/components/BoardColumnMenu.tsx @@ -5,7 +5,6 @@ import { Key } from 'ts-key-enum'; import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress'; import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { IconPencil, IconPlus, IconTrash } from '@/ui/icon'; @@ -13,8 +12,9 @@ import { SingleEntitySelect } from '@/ui/input/relation-picker/components/Single import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar'; -import { icon } from '@/ui/theme/constants/icon'; +import { ThemeColor } from '@/ui/theme/constants/colors'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -32,7 +32,7 @@ const StyledMenuContainer = styled.div` `; type OwnProps = { - color: string; + color: ThemeColor; onClose: () => void; onDelete?: (id: string) => void; onTitleEdit: (title: string, color: string) => void; @@ -52,7 +52,7 @@ export function BoardColumnMenu({ }: OwnProps) { const [currentMenu, setCurrentMenu] = useState('actions'); - const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); + const [, setBoardColumns] = useRecoilState(boardColumnsState); const boardColumnMenuRef = useRef(null); @@ -130,21 +130,21 @@ export function BoardColumnMenu({ {currentMenu === 'actions' && ( - setMenu('title')}> - - Rename - - setMenu('title')} + LeftIcon={IconPencil} + text="Rename" + /> + - - Delete - - setMenu('add')}> - - New opportunity - + LeftIcon={IconTrash} + text="Delete" + /> + setMenu('add')} + LeftIcon={IconPlus} + text="New opportunity" + /> )} {currentMenu === 'title' && ( diff --git a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx index 77b02eae9b..69bd1ae0a2 100644 --- a/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx +++ b/front/src/modules/ui/board/components/BoardOptionsDropdownContent.tsx @@ -7,18 +7,19 @@ import { v4 } from 'uuid'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconChevronLeft, - IconChevronRight, IconLayoutKanban, IconPlus, IconSettings, } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate'; +import { ThemeColor } from '@/ui/theme/constants/colors'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -35,16 +36,18 @@ const StyledIconSettings = styled(IconSettings)` margin-right: ${({ theme }) => theme.spacing(1)}; `; -const StyledIconChevronRight = styled(IconChevronRight)` - color: ${({ theme }) => theme.font.color.tertiary}; - margin-left: auto; -`; - enum BoardOptionsMenu { StageCreation = 'StageCreation', Stages = 'Stages', } +type ColumnForCreate = { + id: string; + colorCode: ThemeColor; + index: number; + title: string; +}; + export function BoardOptionsDropdownContent({ customHotkeyScope, onStageAdd, @@ -68,7 +71,7 @@ export function BoardOptionsDropdownContent({ ) return; - const columnToCreate = { + const columnToCreate: ColumnForCreate = { id: v4(), colorCode: 'gray', index: boardColumns.length, @@ -113,32 +116,26 @@ export function BoardOptionsDropdownContent({
- setCurrentMenu(BoardOptionsMenu.Stages)} - > - - Stages - - + LeftIcon={IconLayoutKanban} + text="Stages" + /> )} {currentMenu === BoardOptionsMenu.Stages && ( <> - } - onClick={resetMenu} - > + Stages - setCurrentMenu(BoardOptionsMenu.StageCreation)} - > - - Add stage - + LeftIcon={IconPlus} + text="Add stage" + /> )} diff --git a/front/src/modules/ui/board/components/__stories__/BoardColumnEditTitleMenu.stories.tsx b/front/src/modules/ui/board/components/__stories__/BoardColumnEditTitleMenu.stories.tsx index c26c6ca4f2..6f6190a52e 100644 --- a/front/src/modules/ui/board/components/__stories__/BoardColumnEditTitleMenu.stories.tsx +++ b/front/src/modules/ui/board/components/__stories__/BoardColumnEditTitleMenu.stories.tsx @@ -4,7 +4,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { BoardColumnEditTitleMenu, - COLOR_OPTIONS, + COLUMN_COLOR_OPTIONS, } from '../BoardColumnEditTitleMenu'; const meta: Meta = { @@ -14,7 +14,7 @@ const meta: Meta = { argTypes: { color: { control: 'select', - options: COLOR_OPTIONS.map(({ id }) => id), + options: COLUMN_COLOR_OPTIONS.map(({ id }) => id), }, }, args: { color: 'green', title: 'Column title' }, diff --git a/front/src/modules/ui/board/hooks/useBoardContextMenuEntries.tsx b/front/src/modules/ui/board/hooks/useBoardContextMenuEntries.tsx index be0178f8e7..29fee01e0e 100644 --- a/front/src/modules/ui/board/hooks/useBoardContextMenuEntries.tsx +++ b/front/src/modules/ui/board/hooks/useBoardContextMenuEntries.tsx @@ -16,7 +16,7 @@ export function useBoardContextMenuEntries() { setContextMenuEntries([ } + Icon={IconTrash} accent="danger" onClick={() => deleteSelectedBoardCards()} key="delete" diff --git a/front/src/modules/ui/board/types/BoardColumnDefinition.ts b/front/src/modules/ui/board/types/BoardColumnDefinition.ts index ce7be9befb..ecb92214fb 100644 --- a/front/src/modules/ui/board/types/BoardColumnDefinition.ts +++ b/front/src/modules/ui/board/types/BoardColumnDefinition.ts @@ -1,6 +1,8 @@ +import { ThemeColor } from '@/ui/theme/constants/colors'; + export type BoardColumnDefinition = { id: string; title: string; index: number; - colorCode: string; + colorCode?: ThemeColor; }; diff --git a/front/src/modules/ui/context-menu/components/ContextMenuEntry.tsx b/front/src/modules/ui/context-menu/components/ContextMenuEntry.tsx index c026219a0f..1c07c11b11 100644 --- a/front/src/modules/ui/context-menu/components/ContextMenuEntry.tsx +++ b/front/src/modules/ui/context-menu/components/ContextMenuEntry.tsx @@ -1,32 +1,22 @@ -import { ReactNode } from 'react'; -import styled from '@emotion/styled'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; - -type ContextMenuEntryAccent = 'regular' | 'danger'; +type ContextMenuEntryAccent = 'default' | 'danger'; type OwnProps = { - icon: ReactNode; + Icon: IconComponent; label: string; accent?: ContextMenuEntryAccent; onClick: () => void; }; -const StyledButtonLabel = styled.div` - font-weight: ${({ theme }) => theme.font.weight.medium}; - margin-left: ${({ theme }) => theme.spacing(2)}; -`; - export function ContextMenuEntry({ label, - icon, - accent = 'regular', + Icon, + accent = 'default', onClick, }: OwnProps) { return ( - - {icon} - {label} - + ); } diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuCheckableItem.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuCheckableItem.tsx deleted file mode 100644 index bbd4928d07..0000000000 --- a/front/src/modules/ui/dropdown/components/DropdownMenuCheckableItem.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; - -import { Checkbox } from '@/ui/input/checkbox/components/Checkbox'; - -import { DropdownMenuItem } from './DropdownMenuItem'; - -type Props = { - checked: boolean; - onChange?: (newCheckedValue: boolean) => void; - id?: string; -}; - -const StyledDropdownMenuCheckableItemContainer = styled(DropdownMenuItem)` - align-items: center; - display: flex; - justify-content: space-between; -`; - -const StyledLeftContainer = styled.div` - align-items: center; - display: flex; - - gap: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledChildrenContainer = styled.div` - align-items: center; - - display: flex; - font-size: ${({ theme }) => theme.font.size.sm}; - gap: ${({ theme }) => theme.spacing(2)}; -`; - -export function DropdownMenuCheckableItem({ - checked, - onChange, - children, -}: React.PropsWithChildren) { - function handleClick() { - onChange?.(!checked); - } - - return ( - - - - {children} - - - ); -} diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx index f3a7a49d3b..d72c7f2a4b 100644 --- a/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx +++ b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx @@ -1,6 +1,9 @@ -import { ComponentProps, ReactElement } from 'react'; +import { ComponentProps } from 'react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; + const StyledHeader = styled.li` align-items: center; color: ${({ theme }) => theme.font.color.primary}; @@ -40,23 +43,31 @@ const StyledEndIconWrapper = styled(StyledStartIconWrapper)` `; type DropdownMenuHeaderProps = ComponentProps<'li'> & { - startIcon?: ReactElement; - endIcon?: ReactElement; + StartIcon?: IconComponent; + EndIcon?: IconComponent; }; export function DropdownMenuHeader({ children, - startIcon, - endIcon, + StartIcon, + EndIcon, ...props }: DropdownMenuHeaderProps) { + const theme = useTheme(); + return ( - {startIcon && ( - {startIcon} + {StartIcon && ( + + + )} {children} - {endIcon && {endIcon}} + {EndIcon && ( + + + + )} ); } diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx deleted file mode 100644 index f3fd76d42b..0000000000 --- a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { ComponentProps } from 'react'; -import styled from '@emotion/styled'; -import { motion } from 'framer-motion'; - -import { FloatingIconButtonGroup } from '@/ui/button/components/FloatingIconButtonGroup'; -import { hoverBackground } from '@/ui/theme/constants/effects'; - -export type DropdownMenuItemAccent = 'regular' | 'danger'; - -const StyledItem = styled.li<{ accent: DropdownMenuItemAccent }>` - --horizontal-padding: ${({ theme }) => theme.spacing(1)}; - --vertical-padding: ${({ theme }) => theme.spacing(2)}; - - align-items: center; - - border-radius: ${({ theme }) => theme.border.radius.sm}; - color: ${({ theme, accent }) => - accent === 'danger' ? theme.color.red : theme.font.color.secondary}; - - cursor: pointer; - display: flex; - flex-direction: row; - - font-size: ${({ theme }) => theme.font.size.sm}; - - gap: ${({ theme }) => theme.spacing(2)}; - - height: calc(32px - 2 * var(--vertical-padding)); - - padding: var(--vertical-padding) var(--horizontal-padding); - - ${hoverBackground}; - - position: relative; - user-select: none; - - width: calc(100% - 2 * var(--horizontal-padding)); - - &:hover .actions-hover-container { - display: flex; - } -`; - -const StyledActions = styled(motion.div)` - display: none; - position: absolute; - right: ${({ theme }) => theme.spacing(1)}; -`; - -export type DropdownMenuItemProps = ComponentProps<'li'> & { - actions?: React.ReactNode[]; - accent?: DropdownMenuItemAccent; -}; - -export function DropdownMenuItem({ - actions, - children, - accent = 'regular', - ...props -}: DropdownMenuItemProps) { - return ( - - {children} - {actions && ( - - - {actions} - - - )} - - ); -} diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSelectableItem.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuSelectableItem.tsx deleted file mode 100644 index 639fff10b9..0000000000 --- a/front/src/modules/ui/dropdown/components/DropdownMenuSelectableItem.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useEffect } from 'react'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; - -import { IconCheck } from '@/ui/icon/index'; -import { hoverBackground } from '@/ui/theme/constants/effects'; - -import { DropdownMenuItem } from './DropdownMenuItem'; - -type Props = React.ComponentProps<'li'> & { - selected?: boolean; - hovered?: boolean; - disabled?: boolean; -}; - -const StyledDropdownMenuSelectableItemContainer = styled(DropdownMenuItem)< - Pick ->` - ${hoverBackground}; - - align-items: center; - - background: ${(props) => - props.hovered ? props.theme.background.transparent.light : 'transparent'}; - - display: flex; - justify-content: space-between; - - width: calc(100% - ${({ theme }) => theme.spacing(2)}); -`; - -const StyledLeftContainer = styled.div>` - align-items: center; - - display: flex; - - gap: ${({ theme }) => theme.spacing(2)}; - - opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; - - overflow: hidden; -`; - -const StyledRightIcon = styled.div` - display: flex; -`; - -export function DropdownMenuSelectableItem({ - selected, - onClick, - children, - hovered, - disabled, - ...restProps -}: React.PropsWithChildren) { - const theme = useTheme(); - - function handleClick(event: React.MouseEvent) { - if (disabled) { - return; - } - - onClick?.(event); - } - - useEffect(() => { - if (hovered) { - window.scrollTo({ - behavior: 'smooth', - }); - } - }, [hovered]); - - return ( - - {children} - - {selected && } - - - ); -} diff --git a/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx b/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx index 0425bc1a9c..9bc9b6e7c4 100644 --- a/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx +++ b/front/src/modules/ui/dropdown/components/StyledDropdownMenuItemsContainer.tsx @@ -15,5 +15,6 @@ export const StyledDropdownMenuItemsContainer = styled.div<{ overflow-y: auto; padding: var(--padding); + padding-right: var(--padding); width: calc(100% - 2 * var(--padding)); `; diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx index 0b27fef418..4d7c805c7c 100644 --- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx +++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx @@ -2,17 +2,16 @@ import { useState } from 'react'; import styled from '@emotion/styled'; import type { Meta, StoryObj } from '@storybook/react'; -import { IconButton } from '@/ui/button/components/IconButton'; import { IconPlus, IconUser } from '@/ui/icon'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar'; +import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar'; import { Avatar } from '@/users/components/Avatar'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; -import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem'; import { DropdownMenuHeader } from '../DropdownMenuHeader'; import { DropdownMenuInput } from '../DropdownMenuInput'; -import { DropdownMenuItem } from '../DropdownMenuItem'; -import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem'; import { StyledDropdownMenu } from '../StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator'; @@ -101,21 +100,22 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => { return ( <> {mockSelectArray.map((item) => ( - setSelectedItem(item.id)} - > - {hasAvatar && ( - - )} - {item.name} - + avatar={ + hasAvatar ? ( + + ) : undefined + } + text={item.name} + /> ))} ); @@ -127,28 +127,28 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => { return ( <> {mockSelectArray.map((item) => ( - { + selected={selectedItems.includes(item.id)} + onSelectChange={(checked) => { if (checked) { setSelectedItems([...selectedItems, item.id]); } else { setSelectedItems(selectedItems.filter((id) => id !== item.id)); } }} - > - {hasAvatar && ( - - )} - {item.name} - + avatar={ + hasAvatar ? ( + + ) : undefined + } + text={item.name} + /> ))} ); @@ -182,7 +182,7 @@ export const SimpleMenuItem: Story = { {mockSelectArray.map(({ name }) => ( - {name} + ))} @@ -198,14 +198,14 @@ export const WithHeaders: Story = { Subheader 1 {mockSelectArray.slice(0, 3).map(({ name }) => ( - {name} + ))} Subheader 2 {mockSelectArray.slice(3).map(({ name }) => ( - {name} + ))} @@ -218,10 +218,7 @@ export const WithIcons: Story = { {mockSelectArray.map(({ name }) => ( - - - {name} - + ))} @@ -234,15 +231,11 @@ export const WithActions: Story = { {mockSelectArray.map(({ name }, index) => ( - } />, - } />, - ]} - > - {name} - + iconButtons={[{ Icon: IconUser }, { Icon: IconPlus }]} + text={name} + /> ))} @@ -273,7 +266,7 @@ export const Search: Story = { {mockSelectArray.map(({ name }) => ( - {name} + ))} diff --git a/front/src/modules/ui/editable-field/components/EditableField.tsx b/front/src/modules/ui/editable-field/components/EditableField.tsx index c1eee60b3e..682140f732 100644 --- a/front/src/modules/ui/editable-field/components/EditableField.tsx +++ b/front/src/modules/ui/editable-field/components/EditableField.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { useEditableField } from '../hooks/useEditableField'; @@ -71,7 +72,7 @@ const StyledEditableFieldBaseContainer = styled.div` `; type OwnProps = { - iconLabel?: React.ReactNode; + IconLabel?: IconComponent; label?: string; labelFixedWidth?: number; useEditButton?: boolean; @@ -87,7 +88,7 @@ type OwnProps = { }; export function EditableField({ - iconLabel, + IconLabel, label, labelFixedWidth, useEditButton, @@ -125,7 +126,11 @@ export function EditableField({ onMouseLeave={handleContainerMouseLeave} > - {iconLabel && {iconLabel}} + {IconLabel && ( + + + + )} {label && ( {label} )} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx index 2c585ed4c3..0c19985ad6 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableBooleanField.tsx @@ -18,7 +18,7 @@ export function GenericEditableBooleanField() { return ( } displayModeContentOnly /> diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx index 65c698d332..727a9faf0c 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx @@ -32,7 +32,7 @@ export function GenericEditableDateField() { return ( } displayModeContent={} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx index 80cf830914..4ac725cf2e 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx @@ -31,7 +31,7 @@ export function GenericEditableNumberField() { return ( } displayModeContent={fieldValue} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx b/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx index 5ca81dfdfa..14cd9d00c1 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditablePhoneField.tsx @@ -33,7 +33,7 @@ export function GenericEditablePhoneField() { } displayModeContent={} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx index b566983b99..4f874ece12 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx @@ -38,7 +38,7 @@ export function GenericEditableRelationField() { customEditHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker, }} - iconLabel={currentEditableFieldDefinition.icon} + IconLabel={currentEditableFieldDefinition.Icon} editModeContent={} displayModeContent={} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx index 2308f47b67..4927457820 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableTextField.tsx @@ -31,7 +31,7 @@ export function GenericEditableTextField() { return ( } displayModeContent={fieldValue} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx index 29d5392da0..b67e75d29d 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableURLField.tsx @@ -33,7 +33,7 @@ export function GenericEditableURLField() { } displayModeContent={} isDisplayModeContentEmpty={!fieldValue} diff --git a/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx index 83a5d34161..ba114063bb 100644 --- a/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx +++ b/front/src/modules/ui/editable-field/components/ProbabilityEditableField.tsx @@ -18,7 +18,7 @@ export function ProbabilityEditableField() { return ( } displayModeContentOnly disableHoverEffect diff --git a/front/src/modules/ui/editable-field/types/FieldDefinition.ts b/front/src/modules/ui/editable-field/types/FieldDefinition.ts index 82dd1306f5..4904b14405 100644 --- a/front/src/modules/ui/editable-field/types/FieldDefinition.ts +++ b/front/src/modules/ui/editable-field/types/FieldDefinition.ts @@ -1,9 +1,11 @@ +import { IconComponent } from '@/ui/icon/types/IconComponent'; + import { FieldMetadata, FieldType } from './FieldMetadata'; export type FieldDefinition = { key: string; name: string; - icon?: JSX.Element; + Icon?: IconComponent; type: FieldType; metadata: T; }; diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts index 55288f2e60..8c29e830f1 100644 --- a/front/src/modules/ui/editable-field/types/ViewField.ts +++ b/front/src/modules/ui/editable-field/types/ViewField.ts @@ -1,3 +1,4 @@ +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; @@ -118,7 +119,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & ( export type ViewFieldDefinition = { key: string; name: string; - icon?: JSX.Element; + Icon?: IconComponent; isVisible?: boolean; metadata: T; }; diff --git a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx index 987fd8a0ad..3fe2d34551 100644 --- a/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx +++ b/front/src/modules/ui/editable-field/variants/components/DateEditableField.tsx @@ -1,5 +1,6 @@ import { EditableField } from '@/ui/editable-field/components/EditableField'; import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { parseDate } from '~/utils/date-utils'; @@ -7,13 +8,13 @@ import { parseDate } from '~/utils/date-utils'; import { EditableFieldEditModeDate } from './EditableFieldEditModeDate'; type OwnProps = { - icon?: React.ReactNode; + Icon?: IconComponent; label?: string; value: string | null | undefined; onSubmit?: (newValue: string) => void; }; -export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) { +export function DateEditableField({ Icon, value, label, onSubmit }: OwnProps) { async function handleChange(newValue: string) { onSubmit?.(newValue); } @@ -25,7 +26,7 @@ export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) { void; }; export function PhoneEditableField({ - icon, + Icon, placeholder, value, onSubmit, @@ -44,7 +45,7 @@ export function PhoneEditableField({ = { component: DateEditableField, decorators: [ComponentDecorator], argTypes: { - icon: { + Icon: { type: 'boolean', mapping: { - true: , + true: IconCalendar, false: undefined, }, }, @@ -21,7 +21,7 @@ const meta: Meta = { }, args: { value: new Date().toISOString(), - icon: true, + Icon: IconCalendar, }, }; diff --git a/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx b/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx index 1ee3f34bdf..78d09c094d 100644 --- a/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx +++ b/front/src/modules/ui/editable-field/variants/components/__stories__/PhoneEditableField.stories.tsx @@ -10,17 +10,17 @@ const meta: Meta = { component: PhoneEditableField, decorators: [ComponentWithRouterDecorator], argTypes: { - icon: { + Icon: { type: 'boolean', mapping: { - true: , + true: IconPhone, false: undefined, }, }, }, args: { value: '+33714446494', - icon: true, + Icon: IconPhone, placeholder: 'Phone', }, }; diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx index 1b310875e2..5ae588685e 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx @@ -1,8 +1,8 @@ import { Context } from 'react'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -43,8 +43,9 @@ export function FilterDropdownFilterSelect({ return ( {availableFilters.map((availableFilter, index) => ( - { setFilterDefinitionUsedInDropdown(availableFilter); @@ -58,10 +59,9 @@ export function FilterDropdownFilterSelect({ setFilterDropdownSearchInput(''); }} - > - {availableFilter.icon} - {availableFilter.label} - + LeftIcon={availableFilter.Icon} + text={availableFilter.label} + /> ))} ); diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx index 0c34ff520a..0dde7dd27b 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx @@ -1,5 +1,4 @@ import { Context } from 'react'; -import { useTheme } from '@emotion/react'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { IconChevronDown } from '@/ui/icon'; @@ -14,8 +13,6 @@ export function FilterDropdownOperandButton({ }: { context: Context; }) { - const theme = useTheme(); - const [selectedOperandInDropdown] = useRecoilScopedState( selectedOperandInDropdownScopedState, context, @@ -36,7 +33,7 @@ export function FilterDropdownOperandButton({ return ( } + EndIcon={IconChevronDown} onClick={() => setIsFilterDropdownOperandSelectUnfolded(true)} > {getOperandLabel(selectedOperandInDropdown)} diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx index 3bb430e14d..115e83a902 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandSelect.tsx @@ -1,7 +1,7 @@ import { Context } from 'react'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited'; @@ -66,14 +66,13 @@ export function FilterDropdownOperandSelect({ return ( {operandsForFilterType.map((filterOperand, index) => ( - { handleOperangeChange(filterOperand); }} - > - {getOperandLabel(filterOperand)} - + text={getOperandLabel(filterOperand)} + /> ))} ); diff --git a/front/src/modules/ui/filter-n-sort/components/SortAndFilterBar.tsx b/front/src/modules/ui/filter-n-sort/components/SortAndFilterBar.tsx index 01b66d1ff1..61c6b9833b 100644 --- a/front/src/modules/ui/filter-n-sort/components/SortAndFilterBar.tsx +++ b/front/src/modules/ui/filter-n-sort/components/SortAndFilterBar.tsx @@ -158,14 +158,12 @@ function SortAndFilterBar({ return ( - ) : ( - - ) + Icon={ + sort.order === 'desc' + ? IconArrowNarrowDown + : IconArrowNarrowUp } isSort onRemove={() => onRemoveSort(sort.key)} @@ -181,12 +179,12 @@ function SortAndFilterBar({ return ( { removeFilter(filter.key); }} diff --git a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx index db860dce93..ba878757ed 100644 --- a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx @@ -1,12 +1,10 @@ import { Context, useCallback, useState } from 'react'; -import { useTheme } from '@emotion/react'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconChevronDown } from '@/ui/icon'; -import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { SelectedSortType, SortType } from '../types/interface'; @@ -30,8 +28,6 @@ export function SortDropdownButton({ onSortSelect, HotkeyScope, }: OwnProps) { - const theme = useTheme(); - const [isUnfolded, setIsUnfolded] = useState(false); const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); const [selectedSortDirection, setSelectedSortDirection] = @@ -74,21 +70,20 @@ export function SortDropdownButton({ {isOptionUnfolded ? ( {options.map((option, index) => ( - { setSelectedSortDirection(option); setIsOptionUnfolded(false); }} - > - {option === 'asc' ? 'Ascending' : 'Descending'} - + text={option === 'asc' ? 'Ascending' : 'Descending'} + /> ))} ) : ( <> } + EndIcon={IconChevronDown} onClick={() => setIsOptionUnfolded(true)} > {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} @@ -97,13 +92,13 @@ export function SortDropdownButton({ {availableSorts.map((sort, index) => ( - handleAddSort(sort)} - > - {sort.icon} - - + LeftIcon={sort.Icon} + text={sort.label} + /> ))} diff --git a/front/src/modules/ui/filter-n-sort/components/SortOrFilterChip.tsx b/front/src/modules/ui/filter-n-sort/components/SortOrFilterChip.tsx index aa72e88c6b..80e2e4e85a 100644 --- a/front/src/modules/ui/filter-n-sort/components/SortOrFilterChip.tsx +++ b/front/src/modules/ui/filter-n-sort/components/SortOrFilterChip.tsx @@ -1,16 +1,16 @@ -import { ReactNode } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { IconX } from '@/ui/icon/index'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; type OwnProps = { - id: string; labelKey?: string; labelValue: string; - icon: ReactNode; + Icon?: IconComponent; onRemove: () => void; isSort?: boolean; + testId?: string; }; type StyledChipProps = { @@ -55,20 +55,24 @@ const StyledLabelKey = styled.div` `; function SortOrFilterChip({ - id, labelKey, labelValue, - icon, + Icon, onRemove, isSort, + testId, }: OwnProps) { const theme = useTheme(); return ( - {icon} + {Icon && ( + + + + )} {labelKey && {labelKey}} {labelValue} - + diff --git a/front/src/modules/ui/filter-n-sort/types/FilterDefinition.ts b/front/src/modules/ui/filter-n-sort/types/FilterDefinition.ts index 34e2751fe1..b08ff8bda4 100644 --- a/front/src/modules/ui/filter-n-sort/types/FilterDefinition.ts +++ b/front/src/modules/ui/filter-n-sort/types/FilterDefinition.ts @@ -1,9 +1,11 @@ +import { IconComponent } from '@/ui/icon/types/IconComponent'; + import { FilterType } from './FilterType'; export type FilterDefinition = { key: string; label: string; - icon: JSX.Element; + Icon: IconComponent; type: FilterType; entitySelectComponent?: JSX.Element; }; diff --git a/front/src/modules/ui/filter-n-sort/types/interface.ts b/front/src/modules/ui/filter-n-sort/types/interface.ts index 0cba99a243..3bb4e43dd8 100644 --- a/front/src/modules/ui/filter-n-sort/types/interface.ts +++ b/front/src/modules/ui/filter-n-sort/types/interface.ts @@ -1,11 +1,10 @@ -import { ReactNode } from 'react'; - +import { IconComponent } from '@/ui/icon/types/IconComponent'; import { SortOrder as Order_By } from '~/generated/graphql'; export type SortType = { label: string; key: string; - icon?: ReactNode; + Icon?: IconComponent; orderByTemplate?: (order: Order_By) => OrderByTemplate[]; }; diff --git a/front/src/modules/ui/icon/types/IconComponent.ts b/front/src/modules/ui/icon/types/IconComponent.ts index 26c42b0260..edcc48f5dd 100644 --- a/front/src/modules/ui/icon/types/IconComponent.ts +++ b/front/src/modules/ui/icon/types/IconComponent.ts @@ -1,3 +1,3 @@ -import { ComponentType } from 'react'; +import { FunctionComponent } from 'react'; -export type IconComponent = ComponentType<{ size: number }>; +export type IconComponent = FunctionComponent<{ size?: number }>; diff --git a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx index 943d6ee3aa..1cbe79a9a7 100644 --- a/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/MultipleEntitySelect.tsx @@ -1,12 +1,12 @@ import { useRef } from 'react'; import debounce from 'lodash.debounce'; -import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { Avatar } from '@/users/components/Avatar'; import { isNonEmptyString } from '~/utils/isNonEmptyString'; @@ -81,26 +81,25 @@ export function MultipleEntitySelect< {entitiesInDropdown?.map((entity) => ( - + selected={value[entity.id]} + onSelectChange={(newCheckedValue) => onChange({ ...value, [entity.id]: newCheckedValue }) } - > - - {entity.name} - + avatar={ + + } + text={entity.name} + /> ))} - {entitiesInDropdown?.length === 0 && ( - No result - )} + {entitiesInDropdown?.length === 0 && } ); diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx index 54e8e89670..ece4d9a56a 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelect.tsx @@ -1,12 +1,11 @@ import { useRef } from 'react'; -import { useTheme } from '@emotion/react'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { IconPlus } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { isDefined } from '~/utils/isDefined'; @@ -39,8 +38,6 @@ export function SingleEntitySelect< }) { const containerRef = useRef(null); - const theme = useTheme(); - const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch(); const showCreateButton = isDefined(onCreate) && searchFilter !== ''; @@ -76,10 +73,7 @@ export function SingleEntitySelect< {showCreateButton && ( <> - - - Add New - + diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index bb8481c30a..a91e10b06d 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -1,12 +1,10 @@ import { useRef } from 'react'; -import { useTheme } from '@emotion/react'; import { Key } from 'ts-key-enum'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; -import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { IconBuildingSkyscraper, IconUserCircle } from '@/ui/icon'; -import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { Avatar } from '@/users/components/Avatar'; import { isDefined } from '~/utils/isDefined'; @@ -76,43 +74,44 @@ export function SingleEntitySelectBase< entitiesInDropdown = entitiesInDropdown.filter((entity) => isNonEmptyString(entity.name.trim()), ); - const theme = useTheme(); + + const NoUserIcon = + noUser?.entityType === Entity.User + ? IconUserCircle + : IconBuildingSkyscraper; return ( {noUser && ( - onEntitySelected(noUser)}> - {noUser.entityType === Entity.User ? ( - - ) : ( - - )} - {noUser.name} - + onEntitySelected(noUser)} + LeftIcon={NoUserIcon} + text={noUser.name} + /> )} {entities.loading ? ( ) : entitiesInDropdown.length === 0 ? ( - No result + ) : ( - entitiesInDropdown?.map((entity, index) => ( - ( + onEntitySelected(entity)} - > - - - + text={entity.name} + hovered={hoveredIndex === entitiesInDropdown.indexOf(entity)} + avatar={ + + } + /> )) )} diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx index 648d050a68..2250632486 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx @@ -4,12 +4,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; import { IconButton } from '@/ui/button/components/IconButton'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { ActivityType } from '~/generated/graphql'; const StyledContainer = styled.div` @@ -50,20 +50,18 @@ export function ShowPageAddButton({ e.stopPropagation()} > - handleSelect(ActivityType.Note)} - accent="regular" - > - - Note - - handleSelect(ActivityType.Task)} - accent="regular" - > - - Task - + accent="default" + LeftIcon={IconNotes} + text="Note" + /> + handleSelect(ActivityType.Note)} + accent="default" + LeftIcon={IconCheckbox} + text="Task" + /> } diff --git a/front/src/modules/ui/menu-item/components/MenuItem.tsx b/front/src/modules/ui/menu-item/components/MenuItem.tsx index 4d701e3df9..0ad2e593fa 100644 --- a/front/src/modules/ui/menu-item/components/MenuItem.tsx +++ b/front/src/modules/ui/menu-item/components/MenuItem.tsx @@ -1,3 +1,4 @@ +import { MouseEvent } from 'react'; import { useTheme } from '@emotion/react'; import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; @@ -10,24 +11,26 @@ import { MenuItemAccent } from '../types/MenuItemAccent'; export type MenuItemIconButton = { Icon: IconComponent; - onClick: () => void; + onClick?: (event: MouseEvent) => void; }; export type MenuItemProps = { - LeftIcon?: IconComponent; - accent: MenuItemAccent; + LeftIcon?: IconComponent | null; + accent?: MenuItemAccent; text: string; iconButtons?: MenuItemIconButton[]; - className: string; + className?: string; + testId?: string; onClick?: () => void; }; export function MenuItem({ LeftIcon, - accent, + accent = 'default', text, iconButtons, className, + testId, onClick, }: MenuItemProps) { const theme = useTheme(); @@ -35,8 +38,13 @@ export function MenuItem({ const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0; return ( - - + + {showIconButtons && ( {iconButtons?.map(({ Icon, onClick }, index) => ( diff --git a/front/src/modules/ui/menu-item/components/MenuItemMultiSelectAvatar.tsx b/front/src/modules/ui/menu-item/components/MenuItemMultiSelectAvatar.tsx new file mode 100644 index 0000000000..03f09e9b73 --- /dev/null +++ b/front/src/modules/ui/menu-item/components/MenuItemMultiSelectAvatar.tsx @@ -0,0 +1,51 @@ +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; + +import { Checkbox } from '@/ui/input/checkbox/components/Checkbox'; + +import { + StyledMenuItemBase, + StyledMenuItemLabel, + StyledMenuItemLeftContent, +} from '../internals/components/StyledMenuItemBase'; + +const StyledLeftContentWithCheckboxContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +type OwnProps = { + avatar?: ReactNode; + selected: boolean; + text: string; + className?: string; + onSelectChange?: (selected: boolean) => void; +}; + +export function MenuItemMultiSelectAvatar({ + avatar, + text, + selected, + className, + onSelectChange, +}: OwnProps) { + function handleOnClick() { + onSelectChange?.(!selected); + } + + return ( + + + + + {avatar} + + {text} + + + + + ); +} diff --git a/front/src/modules/ui/menu-item/components/MenuItemNavigate.tsx b/front/src/modules/ui/menu-item/components/MenuItemNavigate.tsx index 0ae19e95f0..4fe76c0c50 100644 --- a/front/src/modules/ui/menu-item/components/MenuItemNavigate.tsx +++ b/front/src/modules/ui/menu-item/components/MenuItemNavigate.tsx @@ -10,7 +10,7 @@ export type MenuItemProps = { LeftIcon?: IconComponent; text: string; onClick?: () => void; - className: string; + className?: string; }; export function MenuItemNavigate({ diff --git a/front/src/modules/ui/menu-item/components/MenuItemSelect.tsx b/front/src/modules/ui/menu-item/components/MenuItemSelect.tsx index 6502e1438a..623530e5a6 100644 --- a/front/src/modules/ui/menu-item/components/MenuItemSelect.tsx +++ b/front/src/modules/ui/menu-item/components/MenuItemSelect.tsx @@ -7,8 +7,12 @@ import { IconComponent } from '@/ui/icon/types/IconComponent'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; -const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>` - ${({ theme, selected }) => { +export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ + selected: boolean; + disabled?: boolean; + hovered?: boolean; +}>` + ${({ theme, selected, disabled, hovered }) => { if (selected) { return css` background: ${theme.background.transparent.light}; @@ -16,16 +20,33 @@ const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>` background: ${theme.background.transparent.medium}; } `; + } else if (disabled) { + return css` + background: inherit; + &:hover { + background: inherit; + } + + color: ${theme.font.color.tertiary}; + + cursor: default; + `; + } else if (hovered) { + return css` + background: ${theme.background.transparent.light}; + `; } }} `; type OwnProps = { - LeftIcon?: IconComponent; + LeftIcon: IconComponent | null | undefined; selected: boolean; text: string; - className: string; + className?: string; onClick?: () => void; + disabled?: boolean; + hovered?: boolean; }; export function MenuItemSelect({ @@ -34,6 +55,8 @@ export function MenuItemSelect({ selected, className, onClick, + disabled, + hovered, }: OwnProps) { const theme = useTheme(); @@ -42,6 +65,8 @@ export function MenuItemSelect({ onClick={onClick} className={className} selected={selected} + disabled={disabled} + hovered={hovered} > {selected && } diff --git a/front/src/modules/ui/menu-item/components/MenuItemSelectAvatar.tsx b/front/src/modules/ui/menu-item/components/MenuItemSelectAvatar.tsx new file mode 100644 index 0000000000..10f8afdd00 --- /dev/null +++ b/front/src/modules/ui/menu-item/components/MenuItemSelectAvatar.tsx @@ -0,0 +1,55 @@ +import { ReactNode } from 'react'; +import { useTheme } from '@emotion/react'; + +import { IconCheck } from '@/ui/icon'; +import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; + +import { + StyledMenuItemLabel, + StyledMenuItemLeftContent, +} from '../internals/components/StyledMenuItemBase'; + +import { StyledMenuItemSelect } from './MenuItemSelect'; + +type OwnProps = { + avatar: ReactNode; + selected: boolean; + text: string; + className?: string; + onClick?: () => void; + disabled?: boolean; + hovered?: boolean; + testId?: string; +}; + +export function MenuItemSelectAvatar({ + avatar, + text, + selected, + className, + onClick, + disabled, + hovered, + testId, +}: OwnProps) { + const theme = useTheme(); + + return ( + + + {avatar} + + + + + {selected && } + + ); +} diff --git a/front/src/modules/ui/menu-item/components/MenuItemSelectColor.tsx b/front/src/modules/ui/menu-item/components/MenuItemSelectColor.tsx new file mode 100644 index 0000000000..b2de9a2748 --- /dev/null +++ b/front/src/modules/ui/menu-item/components/MenuItemSelectColor.tsx @@ -0,0 +1,59 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { IconCheck } from '@/ui/icon'; +import { ThemeColor } from '@/ui/theme/constants/colors'; + +import { + StyledMenuItemLabel, + StyledMenuItemLeftContent, +} from '../internals/components/StyledMenuItemBase'; + +import { StyledMenuItemSelect } from './MenuItemSelect'; + +const StyledColorSample = styled.div<{ colorName: ThemeColor }>` + background-color: ${({ theme, colorName }) => + theme.tag.background[colorName]}; + border: 1px solid ${({ theme, colorName }) => theme.color[colorName]}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + height: 12px; + width: 12px; +`; + +type OwnProps = { + selected: boolean; + text: string; + className?: string; + onClick?: () => void; + disabled?: boolean; + hovered?: boolean; + color: ThemeColor; +}; + +export function MenuItemSelectColor({ + color, + text, + selected, + className, + onClick, + disabled, + hovered, +}: OwnProps) { + const theme = useTheme(); + + return ( + + + + {text} + + {selected && } + + ); +} diff --git a/front/src/modules/ui/menu-item/internals/components/MenuItemLeftContent.tsx b/front/src/modules/ui/menu-item/internals/components/MenuItemLeftContent.tsx index 5035817296..b0462c5829 100644 --- a/front/src/modules/ui/menu-item/internals/components/MenuItemLeftContent.tsx +++ b/front/src/modules/ui/menu-item/internals/components/MenuItemLeftContent.tsx @@ -1,6 +1,7 @@ import { useTheme } from '@emotion/react'; import { IconComponent } from '@/ui/icon/types/IconComponent'; +import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; import { StyledMenuItemLabel, @@ -8,7 +9,7 @@ import { } from './StyledMenuItemBase'; type OwnProps = { - LeftIcon?: IconComponent; + LeftIcon: IconComponent | null | undefined; text: string; }; @@ -18,7 +19,9 @@ export function MenuItemLeftContent({ LeftIcon, text }: OwnProps) { return ( {LeftIcon && } - {text} + + + ); } diff --git a/front/src/modules/ui/menu-item/internals/components/StyledMenuItemBase.tsx b/front/src/modules/ui/menu-item/internals/components/StyledMenuItemBase.tsx index 73e8dbfa09..5620470b8a 100644 --- a/front/src/modules/ui/menu-item/internals/components/StyledMenuItemBase.tsx +++ b/front/src/modules/ui/menu-item/internals/components/StyledMenuItemBase.tsx @@ -16,9 +16,9 @@ export const StyledMenuItemBase = styled.li` align-items: center; border-radius: ${({ theme }) => theme.border.radius.sm}; - cursor: pointer; display: flex; + flex-direction: row; font-size: ${({ theme }) => theme.font.size.sm}; @@ -29,8 +29,6 @@ export const StyledMenuItemBase = styled.li` justify-content: space-between; - padding: var(--vertical-padding) var(--horizontal-padding); - ${hoverBackground}; ${({ theme, accent }) => { @@ -43,6 +41,11 @@ export const StyledMenuItemBase = styled.li` } `; } + case 'placeholder': { + return css` + color: ${theme.font.color.tertiary}; + `; + } case 'default': default: { return css` @@ -52,7 +55,9 @@ export const StyledMenuItemBase = styled.li` } }} + padding: var(--vertical-padding) var(--horizontal-padding); position: relative; + user-select: none; width: calc(100% - 2 * var(--horizontal-padding)); diff --git a/front/src/modules/ui/menu-item/types/MenuItemAccent.ts b/front/src/modules/ui/menu-item/types/MenuItemAccent.ts index 72013f8a23..c67b7f0472 100644 --- a/front/src/modules/ui/menu-item/types/MenuItemAccent.ts +++ b/front/src/modules/ui/menu-item/types/MenuItemAccent.ts @@ -1 +1 @@ -export type MenuItemAccent = 'default' | 'danger'; +export type MenuItemAccent = 'default' | 'danger' | 'placeholder'; diff --git a/front/src/modules/ui/table/components/ColumnHead.tsx b/front/src/modules/ui/table/components/ColumnHead.tsx index b0f15c4d75..827102423f 100644 --- a/front/src/modules/ui/table/components/ColumnHead.tsx +++ b/front/src/modules/ui/table/components/ColumnHead.tsx @@ -1,9 +1,11 @@ -import { ReactNode } from 'react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { IconComponent } from '@/ui/icon/types/IconComponent'; + type OwnProps = { viewName: string; - viewIcon?: ReactNode; + ViewIcon?: IconComponent; }; const StyledTitle = styled.div` @@ -32,10 +34,13 @@ const StyledText = styled.span` white-space: nowrap; `; -export function ColumnHead({ viewName, viewIcon }: OwnProps) { +export function ColumnHead({ viewName, ViewIcon }: OwnProps) { + const theme = useTheme(); return ( - {viewIcon} + + {ViewIcon && } + {viewName} ); diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx index e763943dcd..9d0a8fef06 100644 --- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx +++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx @@ -1,13 +1,11 @@ -import { cloneElement, type ComponentProps, useCallback, useRef } from 'react'; -import { useTheme } from '@emotion/react'; +import { type ComponentProps, useCallback, useRef } from 'react'; import styled from '@emotion/styled'; -import { IconButton } from '@/ui/button/components/IconButton'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; import { IconPlus } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -31,7 +29,6 @@ export const EntityTableColumnMenu = ({ ...props }: EntityTableColumnMenuProps) => { const ref = useRef(null); - const theme = useTheme(); const hiddenTableColumns = useRecoilScopedValue( hiddenTableColumnsScopedSelector, @@ -57,22 +54,17 @@ export const EntityTableColumnMenu = ({ {hiddenTableColumns.map((column) => ( - } - onClick={() => handleAddColumn(column)} - />, + iconButtons={[ + { + Icon: IconPlus, + onClick: () => handleAddColumn(column), + }, ]} - > - {column.icon && - cloneElement(column.icon, { - size: theme.icon.size.md, - })} - {column.name} - + LeftIcon={column.Icon} + text={column.name} + /> ))} diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index feff50511a..889559990b 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -173,7 +173,7 @@ export function EntityTableHeader() { COLUMN_MIN_WIDTH, )} > - + void; @@ -59,8 +48,6 @@ export function TableOptionsDropdownContent({ onViewsChange, onImport, }: TableOptionsDropdownButtonProps) { - const theme = useTheme(); - const tableScopeId = useContextScopeId(TableRecoilScopeContext); const { closeDropdownButton } = useDropdownButton({ key: 'options' }); @@ -87,33 +74,6 @@ export function TableOptionsDropdownContent({ TableRecoilScopeContext, ); - const { handleColumnVisibilityChange } = useTableColumns(); - - const renderFieldActions = useCallback( - (column: ColumnDefinition) => - // Do not allow hiding last visible column - !column.isVisible || visibleTableColumns.length > 1 - ? [ - - ) : ( - - ) - } - onClick={() => handleColumnVisibilityChange(column)} - />, - ] - : undefined, - [ - handleColumnVisibilityChange, - theme.icon.size.sm, - visibleTableColumns.length, - ], - ); - const resetViewEditMode = useCallback(() => { setTableViewEditMode({ mode: undefined, viewId: undefined }); @@ -232,17 +192,17 @@ export function TableOptionsDropdownContent({ )} - handleSelectOption(Option.Properties)} - > - - Properties - + LeftIcon={IconTag} + text="Properties" + /> {onImport && ( - - - Import - + )} @@ -250,22 +210,20 @@ export function TableOptionsDropdownContent({ {selectedOption === Option.Properties && ( <> } + StartIcon={IconChevronLeft} onClick={resetSelectedOption} > Properties - {hiddenTableColumns.length > 0 && ( <> - diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownSection.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownSection.tsx index 36561193a4..ed9095f480 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdownSection.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownSection.tsx @@ -1,43 +1,39 @@ -import { cloneElement } from 'react'; -import { useTheme } from '@emotion/react'; - -import { - DropdownMenuItem, - DropdownMenuItemProps, -} from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader'; import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; +import { IconMinus, IconPlus } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; +import { useTableColumns } from '../../hooks/useTableColumns'; import type { ColumnDefinition } from '../../types/ColumnDefinition'; -type TableOptionsDropdownSectionProps = { - renderActions: ( - column: ColumnDefinition, - ) => DropdownMenuItemProps['actions']; +type OwnProps = { title: string; columns: ColumnDefinition[]; }; -export function TableOptionsDropdownSection({ - renderActions, +export function TableOptionsDropdownColumnVisibility({ title, columns, -}: TableOptionsDropdownSectionProps) { - const theme = useTheme(); +}: OwnProps) { + const { handleColumnVisibilityChange } = useTableColumns(); return ( <> {title} {columns.map((column) => ( - - {column.icon && - cloneElement(column.icon, { - size: theme.icon.size.md, - })} - {column.name} - + handleColumnVisibilityChange(column), + }, + ]} + text={column.name} + /> ))} diff --git a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx b/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx index 5c517f6e3a..d0f5da945a 100644 --- a/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx +++ b/front/src/modules/ui/table/options/components/TableUpdateViewButtonGroup.tsx @@ -1,12 +1,10 @@ import { useCallback, useState } from 'react'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; import { Button } from '@/ui/button/components/Button'; import { ButtonGroup } from '@/ui/button/components/ButtonGroup'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer'; @@ -17,6 +15,7 @@ import { canPersistFiltersScopedSelector } from '@/ui/filter-n-sort/states/selec import { canPersistSortsScopedSelector } from '@/ui/filter-n-sort/states/selectors/canPersistSortsScopedSelector'; import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState'; import { IconChevronDown, IconPlus } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -45,8 +44,6 @@ export const TableUpdateViewButtonGroup = ({ onViewSubmit, HotkeyScope, }: TableUpdateViewButtonGroupProps) => { - const theme = useTheme(); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); const tableScopeId = useContextScopeId(TableRecoilScopeContext); @@ -153,10 +150,11 @@ export const TableUpdateViewButtonGroup = ({ {isDropdownOpen && ( - - - Create view - + )} diff --git a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx b/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx index 07db3d6156..615e5aecc5 100644 --- a/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx +++ b/front/src/modules/ui/table/options/components/TableViewsDropdownButton.tsx @@ -3,8 +3,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; -import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; -import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; @@ -20,6 +18,7 @@ import { IconPlus, IconTrash, } from '@/ui/icon'; +import { MenuItem } from '@/ui/menu-item/components/MenuItem'; import { currentTableViewIdState, currentTableViewState, @@ -204,37 +203,35 @@ export const TableViewsDropdownButton = ({ > {tableViews.map((view) => ( - handleEditViewButtonClick(event, view.id)} - icon={} - />, - tableViews.length > 1 ? ( - - handleDeleteViewButtonClick(event, view.id) + iconButtons={[ + { + Icon: IconPencil, + onClick: (event: MouseEvent) => + handleEditViewButtonClick(event, view.id), + }, + tableViews.length > 1 + ? { + Icon: IconTrash, + onClick: (event: MouseEvent) => + handleDeleteViewButtonClick(event, view.id), } - icon={} - /> - ) : null, + : null, ].filter(assertNotNull)} onClick={() => handleViewSelect(view.id)} - > - - {view.name} - + LeftIcon={IconList} + text={view.name} + /> ))} - - - Add view - + ); diff --git a/front/src/modules/ui/tag/components/Tag.tsx b/front/src/modules/ui/tag/components/Tag.tsx index 95c508cbf7..3a95896bf5 100644 --- a/front/src/modules/ui/tag/components/Tag.tsx +++ b/front/src/modules/ui/tag/components/Tag.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; +import { ThemeColor } from '@/ui/theme/constants/colors'; + const tagColors = [ 'green', 'turquoise', @@ -40,7 +42,7 @@ const StyledTag = styled.h3<{ `; export type TagProps = { - color: string; + color: ThemeColor; text: string; onClick?: () => void; }; diff --git a/front/src/modules/ui/theme/constants/colors.ts b/front/src/modules/ui/theme/constants/colors.ts index 030026d18e..f3261cd231 100644 --- a/front/src/modules/ui/theme/constants/colors.ts +++ b/front/src/modules/ui/theme/constants/colors.ts @@ -22,8 +22,22 @@ export const grayScale = { gray0: '#ffffff', }; -export const color = { +export const mainColors = { yellow: '#ffd338', + green: '#55ef3c', + turquoise: '#15de8f', + sky: '#00e0ff', + blue: '#1961ed', + purple: '#915ffd', + pink: '#f54bd0', + red: '#f83e3e', + orange: '#ff7222', + gray: grayScale.gray30, +}; + +export type ThemeColor = keyof typeof mainColors; + +export const secondaryColors = { yellow80: '#2e2a1a', yellow70: '#453d1e', yellow60: '#746224', @@ -32,7 +46,7 @@ export const color = { yellow30: '#ffedaf', yellow20: '#fff6d7', yellow10: '#fffbeb', - green: '#55ef3c', + green80: '#1d2d1b', green70: '#23421e', green60: '#2a5822', @@ -41,7 +55,7 @@ export const color = { green30: '#ccfac5', green20: '#ddfcd8', green10: '#eefdec', - turquoise: '#15de8f', + turquoise80: '#172b23', turquoise70: '#173f2f', turquoise60: '#166747', @@ -50,7 +64,7 @@ export const color = { turquoise30: '#a1f2d2', turquoise20: '#d0f8e9', turquoise10: '#e8fcf4', - sky: '#00e0ff', + sky80: '#152b2e', sky70: '#123f45', sky60: '#0e6874', @@ -59,7 +73,7 @@ export const color = { sky30: '#99f3ff', sky20: '#ccf9ff', sky10: '#e5fcff', - blue: '#1961ed', + blue80: '#171e2c', blue70: '#172642', blue60: '#18356d', @@ -68,7 +82,7 @@ export const color = { blue30: '#a3c0f8', blue20: '#d1dffb', blue10: '#e8effd', - purple: '#915ffd', + purple80: '#231e2e', purple70: '#2f2545', purple60: '#483473', @@ -77,7 +91,7 @@ export const color = { purple30: '#d3bffe', purple20: '#e9dfff', purple10: '#f4efff', - pink: '#f54bd0', + pink80: '#2d1c29', pink70: '#43213c', pink60: '#702c61', @@ -86,7 +100,7 @@ export const color = { pink30: '#fbb7ec', pink20: '#fddbf6', pink10: '#feedfa', - red: '#f83e3e', + red80: '#2d1b1b', red70: '#441f1f', red60: '#712727', @@ -95,7 +109,7 @@ export const color = { red30: '#fcb2b2', red20: '#fed8d8', red10: '#feecec', - orange: '#ff7222', + orange80: '#2e2018', orange70: '#452919', orange60: '#743b1b', @@ -104,7 +118,7 @@ export const color = { orange30: '#ffc7a7', orange20: '#ffe3d3', orange10: '#fff1e9', - gray: grayScale.gray30, + gray80: grayScale.gray70, gray70: grayScale.gray65, gray60: grayScale.gray55, @@ -127,6 +141,11 @@ export const color = { blueAccent10: '#f5f9fd', }; +export const color = { + ...mainColors, + ...secondaryColors, +}; + export function rgba(hex: string, alpha: number) { const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(','); return `rgba(${rgb},${alpha})`; diff --git a/front/src/modules/ui/theme/utils/castStringAsThemeColor.ts b/front/src/modules/ui/theme/utils/castStringAsThemeColor.ts new file mode 100644 index 0000000000..b6e8aedce1 --- /dev/null +++ b/front/src/modules/ui/theme/utils/castStringAsThemeColor.ts @@ -0,0 +1,7 @@ +import { mainColors, ThemeColor } from '../constants/colors'; + +export const COLORS = Object.keys(mainColors); + +export function isThemeColor(color: string): color is ThemeColor { + return COLORS.includes(color); +} diff --git a/front/src/pages/companies/__stories__/Companies.filterBy.stories.tsx b/front/src/pages/companies/__stories__/Companies.filterBy.stories.tsx index 55737c39c9..5394bd7f90 100644 --- a/front/src/pages/companies/__stories__/Companies.filterBy.stories.tsx +++ b/front/src/pages/companies/__stories__/Companies.filterBy.stories.tsx @@ -35,11 +35,7 @@ export const FilterByName: Story = { const filterButton = await canvas.findByText('Filter'); await userEvent.click(filterButton); - const nameFilterButton = ( - await canvas.findAllByTestId('dropdown-menu-item') - ).find((item) => { - return item.textContent === 'Name'; - }); + const nameFilterButton = await canvas.findByTestId('select-filter-0'); assert(nameFilterButton); @@ -70,11 +66,9 @@ export const FilterByAccountOwner: Story = { const filterButton = await canvas.findByText('Filter'); await userEvent.click(filterButton); - const accountOwnerFilterButton = ( - await canvas.findAllByTestId('dropdown-menu-item') - ).find((item) => { - return item.textContent === 'Account owner'; - }); + const accountOwnerFilterButton = await canvas.findByTestId( + 'select-filter-5', + ); assert(accountOwnerFilterButton); @@ -89,11 +83,11 @@ export const FilterByAccountOwner: Story = { await sleep(1000); - const charlesChip = ( - await canvas.findAllByTestId('dropdown-menu-item') - ).find((item) => { - return item.textContent?.includes('Charles Test'); - }); + const charlesChip = (await canvas.findAllByTestId('menu-item')).find( + (item) => { + return item.textContent?.includes('Charles Test'); + }, + ); assert(charlesChip); diff --git a/front/src/pages/companies/__stories__/Companies.sortBy.stories.tsx b/front/src/pages/companies/__stories__/Companies.sortBy.stories.tsx index 3b0dbba244..532c2b4c2e 100644 --- a/front/src/pages/companies/__stories__/Companies.sortBy.stories.tsx +++ b/front/src/pages/companies/__stories__/Companies.sortBy.stories.tsx @@ -33,9 +33,8 @@ export const SortByName: Story = { const sortButton = await canvas.findByText('Sort'); await userEvent.click(sortButton); - const nameSortButton = canvas.getByText('Name', { - selector: 'li > div > div', - }); + const nameSortButton = await canvas.findByTestId('select-sort-0'); + await userEvent.click(nameSortButton); expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument(); diff --git a/front/src/pages/companies/companies-filters.tsx b/front/src/pages/companies/companies-filters.tsx index 238aa0b12f..de79892776 100644 --- a/front/src/pages/companies/companies-filters.tsx +++ b/front/src/pages/companies/companies-filters.tsx @@ -8,7 +8,6 @@ import { IconUsers, } from '@/ui/icon/index'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; -import { icon } from '@/ui/theme/constants/icon'; import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect'; import { Company } from '~/generated/graphql'; @@ -16,39 +15,37 @@ export const companiesFilters: FilterDefinitionByEntity[] = [ { key: 'name', label: 'Name', - icon: ( - - ), + Icon: IconBuildingSkyscraper, type: 'text', }, { key: 'employees', label: 'Employees', - icon: , + Icon: IconUsers, type: 'number', }, { key: 'domainName', label: 'URL', - icon: , + Icon: IconLink, type: 'text', }, { key: 'address', label: 'Address', - icon: , + Icon: IconMap, type: 'text', }, { key: 'createdAt', label: 'Created at', - icon: , + Icon: IconCalendarEvent, type: 'date', }, { key: 'accountOwnerId', label: 'Account owner', - icon: , + Icon: IconUser, type: 'entity', entitySelectComponent: ( diff --git a/front/src/pages/companies/companies-sorts.tsx b/front/src/pages/companies/companies-sorts.tsx index df26454837..9c76d8c561 100644 --- a/front/src/pages/companies/companies-sorts.tsx +++ b/front/src/pages/companies/companies-sorts.tsx @@ -12,26 +12,26 @@ export const availableSorts: SortType[] = [ { key: 'name', label: 'Name', - icon: , + Icon: IconBuildingSkyscraper, }, { key: 'employees', label: 'Employees', - icon: , + Icon: IconUsers, }, { key: 'domainName', label: 'Url', - icon: , + Icon: IconLink, }, { key: 'address', label: 'Address', - icon: , + Icon: IconMap, }, { key: 'createdAt', label: 'Creation', - icon: , + Icon: IconCalendarEvent, }, ]; diff --git a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx index 8c4d78a527..ed4402da53 100644 --- a/front/src/pages/companies/constants/companyShowFieldDefinition.tsx +++ b/front/src/pages/companies/constants/companyShowFieldDefinition.tsx @@ -23,7 +23,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'domainName', name: 'Domain name', - icon: , + Icon: IconLink, type: 'url', metadata: { fieldName: 'domainName', @@ -33,7 +33,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'accountOwner', name: 'Account owner', - icon: , + Icon: IconUserCircle, type: 'relation', metadata: { fieldName: 'accountOwner', @@ -43,7 +43,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'employees', name: 'Employees', - icon: , + Icon: IconUsers, type: 'number', metadata: { fieldName: 'employees', @@ -53,7 +53,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'address', name: 'Address', - icon: , + Icon: IconMap, type: 'text', metadata: { fieldName: 'address', @@ -63,7 +63,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'idealCustomerProfile', name: 'ICP', - icon: , + Icon: IconTarget, type: 'boolean', metadata: { fieldName: 'idealCustomerProfile', @@ -72,7 +72,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'xUrl', name: 'Twitter', - icon: , + Icon: IconBrandX, type: 'url', metadata: { fieldName: 'xUrl', @@ -82,7 +82,7 @@ export const companyShowFieldDefinition: FieldDefinition[] = [ { key: 'createdAt', name: 'Created at', - icon: , + Icon: IconCalendar, type: 'date', metadata: { fieldName: 'createdAt', diff --git a/front/src/pages/opportunities/opportunities-filters.tsx b/front/src/pages/opportunities/opportunities-filters.tsx index 17311c076b..06749589a4 100644 --- a/front/src/pages/opportunities/opportunities-filters.tsx +++ b/front/src/pages/opportunities/opportunities-filters.tsx @@ -7,7 +7,6 @@ import { IconCurrencyDollar, IconUser, } from '@/ui/icon/index'; -import { icon } from '@/ui/theme/constants/icon'; import { PipelineProgress } from '~/generated/graphql'; import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect'; @@ -17,21 +16,19 @@ export const opportunitiesFilters: FilterDefinitionByEntity[] { key: 'amount', label: 'Amount', - icon: , + Icon: IconCurrencyDollar, type: 'number', }, { key: 'closeDate', label: 'Close date', - icon: , + Icon: IconCalendarEvent, type: 'date', }, { key: 'companyId', label: 'Company', - icon: ( - - ), + Icon: IconBuildingSkyscraper, type: 'entity', entitySelectComponent: ( [] { key: 'pointOfContactId', label: 'Point of contact', - icon: , + Icon: IconUser, type: 'entity', entitySelectComponent: ( , + Icon: IconCalendarEvent, }, { key: 'amount', label: 'Amount', - icon: , + Icon: IconCurrencyDollar, }, { key: 'closeDate', label: 'Expected close date', - icon: , + Icon: IconCalendarEvent, }, ] satisfies Array>; diff --git a/front/src/pages/people/__stories__/People.filterBy.stories.tsx b/front/src/pages/people/__stories__/People.filterBy.stories.tsx index 32be98a5d4..e3db31dfe2 100644 --- a/front/src/pages/people/__stories__/People.filterBy.stories.tsx +++ b/front/src/pages/people/__stories__/People.filterBy.stories.tsx @@ -35,11 +35,7 @@ export const Email: Story = { const filterButton = await canvas.findByText('Filter'); await userEvent.click(filterButton); - const emailFilterButton = ( - await canvas.findAllByTestId('dropdown-menu-item') - ).find((item) => { - return item.textContent?.includes('Email'); - }); + const emailFilterButton = await canvas.findByTestId('select-filter-2'); assert(emailFilterButton); @@ -70,11 +66,7 @@ export const CompanyName: Story = { const filterButton = await canvas.findByText('Filter'); await userEvent.click(filterButton); - const companyFilterButton = ( - await canvas.findAllByTestId('dropdown-menu-item') - ).find((item) => { - return item.textContent?.includes('Company'); - }); + const companyFilterButton = await canvas.findByTestId('select-filter-3'); assert(companyFilterButton); @@ -87,7 +79,7 @@ export const CompanyName: Story = { await sleep(500); - const qontoChip = (await canvas.findAllByTestId('dropdown-menu-item')).find( + const qontoChip = (await canvas.findAllByTestId('menu-item')).find( (item) => { return item.textContent?.includes('Qonto'); }, diff --git a/front/src/pages/people/__stories__/People.sortBy.stories.tsx b/front/src/pages/people/__stories__/People.sortBy.stories.tsx index d9f7b43108..902f5e0fdd 100644 --- a/front/src/pages/people/__stories__/People.sortBy.stories.tsx +++ b/front/src/pages/people/__stories__/People.sortBy.stories.tsx @@ -34,9 +34,7 @@ export const Email: Story = { const sortButton = await canvas.findByText('Sort'); await userEvent.click(sortButton); - const emailSortButton = canvas.getByText('Email', { - selector: 'li > div > div', - }); + const emailSortButton = await canvas.findByTestId('select-sort-2'); await userEvent.click(emailSortButton); expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument(); @@ -52,9 +50,7 @@ export const Cancel: Story = { const sortButton = await canvas.findByText('Sort'); await userEvent.click(sortButton); - const emailSortButton = canvas.getByText('Email', { - selector: 'li > div > div', - }); + const emailSortButton = await canvas.findByTestId('select-sort-2'); await userEvent.click(emailSortButton); expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument(); diff --git a/front/src/pages/people/constants/personShowFieldDefinition.tsx b/front/src/pages/people/constants/personShowFieldDefinition.tsx index 2917a139bc..63b94a6928 100644 --- a/front/src/pages/people/constants/personShowFieldDefinition.tsx +++ b/front/src/pages/people/constants/personShowFieldDefinition.tsx @@ -23,7 +23,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'email', name: 'Email', - icon: , + Icon: IconMail, type: 'text', metadata: { fieldName: 'email', @@ -33,7 +33,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'company', name: 'Company', - icon: , + Icon: IconBuildingSkyscraper, type: 'relation', metadata: { fieldName: 'company', @@ -44,7 +44,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'phone', name: 'Phone', - icon: , + Icon: IconPhone, type: 'phone', metadata: { fieldName: 'phone', @@ -54,7 +54,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'jobTitle', name: 'Job Title', - icon: , + Icon: IconBriefcase, type: 'text', metadata: { fieldName: 'jobTitle', @@ -64,7 +64,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'city', name: 'City', - icon: , + Icon: IconMap, type: 'text', metadata: { fieldName: 'city', @@ -74,7 +74,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'linkedinUrl', name: 'Linkedin URL', - icon: , + Icon: IconBrandLinkedin, type: 'url', metadata: { fieldName: 'linkedinUrl', @@ -84,7 +84,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'xUrl', name: 'X URL', - icon: , + Icon: IconBrandX, type: 'url', metadata: { fieldName: 'xUrl', @@ -94,7 +94,7 @@ export const personShowFieldDefinition: FieldDefinition[] = [ { key: 'createdAt', name: 'Created at', - icon: , + Icon: IconCalendar, type: 'date', metadata: { fieldName: 'createdAt', diff --git a/front/src/pages/people/people-filters.tsx b/front/src/pages/people/people-filters.tsx index 243802e3c8..e12e9b58bb 100644 --- a/front/src/pages/people/people-filters.tsx +++ b/front/src/pages/people/people-filters.tsx @@ -9,34 +9,31 @@ import { IconUser, } from '@/ui/icon/index'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; -import { icon } from '@/ui/theme/constants/icon'; import { Person } from '~/generated/graphql'; export const peopleFilters: FilterDefinitionByEntity[] = [ { key: 'firstName', label: 'First name', - icon: , + Icon: IconUser, type: 'text', }, { key: 'lastName', label: 'Last name', - icon: , + Icon: IconUser, type: 'text', }, { key: 'email', label: 'Email', - icon: , + Icon: IconMail, type: 'text', }, { key: 'companyId', label: 'Company', - icon: ( - - ), + Icon: IconBuildingSkyscraper, type: 'entity', entitySelectComponent: ( @@ -45,19 +42,19 @@ export const peopleFilters: FilterDefinitionByEntity[] = [ { key: 'phone', label: 'Phone', - icon: , + Icon: IconPhone, type: 'text', }, { key: 'createdAt', label: 'Created at', - icon: , + Icon: IconCalendarEvent, type: 'date', }, { key: 'city', label: 'City', - icon: , + Icon: IconMap, type: 'text', }, ]; diff --git a/front/src/pages/people/people-sorts.tsx b/front/src/pages/people/people-sorts.tsx index 9ece4f20cb..a195b20854 100644 --- a/front/src/pages/people/people-sorts.tsx +++ b/front/src/pages/people/people-sorts.tsx @@ -16,7 +16,7 @@ export const availableSorts: SortType[] = [ { key: 'fullname', label: 'People', - icon: , + Icon: IconUser, orderByTemplate: (order: Order_By) => [ { firstName: order }, @@ -26,28 +26,27 @@ export const availableSorts: SortType[] = [ { key: 'company_name', label: 'Company', - icon: , - + Icon: IconBuildingSkyscraper, orderByTemplate: (order: Order_By) => [{ company: { name: order } }], }, { key: 'email', label: 'Email', - icon: , + Icon: IconMail, }, { key: 'phone', label: 'Phone', - icon: , + Icon: IconPhone, }, { key: 'createdAt', label: 'Created at', - icon: , + Icon: IconCalendarEvent, }, { key: 'city', label: 'City', - icon: , + Icon: IconMap, }, ]; diff --git a/front/src/pages/tasks/tasks-filters.tsx b/front/src/pages/tasks/tasks-filters.tsx index 5f17c16f5a..d7eb368352 100644 --- a/front/src/pages/tasks/tasks-filters.tsx +++ b/front/src/pages/tasks/tasks-filters.tsx @@ -8,7 +8,7 @@ export const tasksFilters: FilterDefinitionByEntity[] = [ { key: 'assigneeId', label: 'Assignee', - icon: , + Icon: IconUser, type: 'entity', entitySelectComponent: (