diff --git a/docs/docs/developer/frontend/best-practices.mdx b/docs/docs/developer/frontend/best-practices.mdx index 3e1d2cf81..c31df21dd 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 d1372bacb..2fe5df664 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 5d01a6265..826893f8c 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 a5d9f7e00..61ae4cb32 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 8a5526511..a096d20e3 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 d75dab85a..77d2d9d03 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 26ddecb0d..4ff2c92fc 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 6ea68721a..362d33af7 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 5cecff827..7dbf90f28 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 5ec6348df..85002c8fb 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 991d94e82..2287a0918 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 ac053e7e8..fbb9e36ca 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 8861b7a4c..b98fa47ee 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 8862d552f..b7341f86e 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 2b1631957..53f0aec88 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 20f87db13..0e1cb67c4 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 db73c7717..81b73a024 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 48a16a58f..2d5365f76 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 20181e469..8c64b995b 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 b480d6fb3..8f11831eb 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 6121e0bde..a251903d2 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 5379384f5..ffacca7c2 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 0e6f2cb9b..bedf04b65 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 7a67f37b5..860234d90 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 6c23866b0..4ffcac132 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 77b02eae9..69bd1ae0a 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 c26c6ca4f..6f6190a52 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 be0178f8e..29fee01e0 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 ce7be9bef..ecb92214f 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 c026219a0..1c07c11b1 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 bbd4928d0..000000000 --- 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 f3a7a49d3..d72c7f2a4 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 f3fd76d42..000000000 --- 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 639fff10b..000000000 --- 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 0425bc1a9..9bc9b6e7c 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 0b27fef41..4d7c805c7 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 c1eee60b3..682140f73 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 2c585ed4c..0c19985ad 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 65c698d33..727a9faf0 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 80cf83091..4ac725cf2 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 5ca81dfdf..14cd9d00c 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 b566983b9..4f874ece1 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 2308f47b6..492745782 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 29d5392da..b67e75d29 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 83a5d3416..ba114063b 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 82dd1306f..4904b1440 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 55288f2e6..8c29e830f 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 987fd8a0a..3fe2d3455 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 1ee3f34bd..78d09c094 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 1b310875e..5ae588685 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 0c34ff520..0dde7dd27 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 3bb430e14..115e83a90 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 01b66d1ff..61c6b9833 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 db860dce9..ba878757e 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 aa72e88c6..80e2e4e85 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 34e2751fe..b08ff8bda 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 0cba99a24..3bb4e43dd 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 26c42b026..edcc48f5d 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 943d6ee3a..1cbe79a9a 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 54e8e8967..ece4d9a56 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 bb8481c30..a91e10b06 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 648d050a6..225063248 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 4d701e3df..0ad2e593f 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 000000000..03f09e9b7 --- /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 0ae19e95f..4fe76c0c5 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 6502e1438..623530e5a 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 000000000..10f8afdd0 --- /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 000000000..b2de9a274 --- /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 503581729..b0462c582 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 73e8dbfa0..5620470b8 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 72013f8a2..c67b7f047 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 b0f15c4d7..827102423 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 e763943dc..9d0a8fef0 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 feff50511..889559990 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 36561193a..ed9095f48 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 5c517f6e3..d0f5da945 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 07db3d615..615e5aecc 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 95c508cbf..3a95896bf 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 030026d18..f3261cd23 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 000000000..b6e8aedce --- /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 55737c39c..5394bd7f9 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 3b0dbba24..532c2b4c2 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 238aa0b12..de7989277 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 df2645483..9c76d8c56 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 8c4d78a52..ed4402da5 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 17311c076..06749589a 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 32be98a5d..e3db31dfe 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 d9f7b4310..902f5e0fd 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 2917a139b..63b94a692 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 243802e3c..e12e9b58b 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 9ece4f20c..a195b2085 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 5f17c16f5..d7eb36835 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: (