Refactor/new menu item (#1448)

* wip

* finished

* Added disabled

* Fixed disabled

* Finished cleaning

* Minor fixes from merge

* Added docs

* Added PascalCase

* Fix from review

* Fixes from merge

* Fix lint

* Fixed storybook tests
This commit is contained in:
Lucas Bordeau
2023-09-06 16:41:26 +02:00
committed by GitHub
parent 5c7660f588
commit 28ca9a9e49
96 changed files with 816 additions and 918 deletions

View File

@@ -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 (
<MyComponent Icon={MyIcon} />
)
}
// In MyComponent
function MyComponent({ MyIcon }: { MyIcon: IconComponent }) {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
}
```
For React to understand that the component is a component, you need to use PascalCase, to later instanciate it with `<MyIcon>`
```tsx
## Prop Drilling: Keep It Minimal ## 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: 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:

View File

@@ -71,6 +71,7 @@
"lint": "eslint src --max-warnings=0", "lint": "eslint src --max-warnings=0",
"storybook:dev": "storybook dev -p 6006 -s ../public", "storybook:dev": "storybook dev -p 6006 -s ../public",
"storybook:test": "test-storybook", "storybook:test": "test-storybook",
"storybook:test-slow": "test-storybook --maxWorkers=3",
"storybook:build": "storybook build -s public", "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", "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", "graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js",

View File

@@ -185,7 +185,7 @@ export function ActivityEditor({
<> <>
<DateEditableField <DateEditableField
value={activity.dueAt} value={activity.dueAt}
icon={<IconCalendar />} Icon={IconCalendar}
label="Due date" label="Due date"
onSubmit={(newDate) => { onSubmit={(newDate) => {
updateActivityMutation({ updateActivityMutation({

View File

@@ -23,7 +23,7 @@ export function ActivityAssigneeEditableField({ activity }: OwnProps) {
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}
label="Assignee" label="Assignee"
iconLabel={<IconUserCircle />} IconLabel={IconUserCircle}
editModeContent={ editModeContent={
<ActivityAssigneeEditableFieldEditMode activity={activity} /> <ActivityAssigneeEditableFieldEditMode activity={activity} />
} }

View File

@@ -28,7 +28,7 @@ export function ActivityRelationEditableField({ activity }: OwnProps) {
customEditHotkeyScope={{ customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}
iconLabel={<IconArrowUpRight />} IconLabel={IconArrowUpRight}
editModeContent={ editModeContent={
<ActivityRelationEditableFieldEditMode activity={activity} /> <ActivityRelationEditableFieldEditMode activity={activity} />
} }

View File

@@ -167,7 +167,7 @@ export function CompanyBoardCard() {
value={{ value={{
key: viewField.key, key: viewField.key,
name: viewField.name, name: viewField.name,
icon: viewField.icon, Icon: viewField.Icon,
type: viewField.metadata.type, type: viewField.metadata.type,
metadata: viewField.metadata, metadata: viewField.metadata,
}} }}

View File

@@ -1,11 +1,9 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; 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 { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; 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 { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery'; import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
@@ -47,8 +46,6 @@ export function CompanyProgressPicker({
string | null string | null
>(null); >(null);
const theme = useTheme();
const [currentPipeline] = useRecoilState(currentPipelineState); const [currentPipeline] = useRecoilState(currentPipelineState);
const currentPipelineStages = useMemo( const currentPipelineStages = useMemo(
@@ -89,22 +86,21 @@ export function CompanyProgressPicker({
{isProgressSelectionUnfolded ? ( {isProgressSelectionUnfolded ? (
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{currentPipelineStages.map((pipelineStage, index) => ( {currentPipelineStages.map((pipelineStage, index) => (
<DropdownMenuItem <MenuItem
key={pipelineStage.id} key={pipelineStage.id}
data-testid={`select-pipeline-stage-${index}`} testId={`select-pipeline-stage-${index}`}
onClick={() => { onClick={() => {
handlePipelineStageChange(pipelineStage.id); handlePipelineStageChange(pipelineStage.id);
}} }}
> text={pipelineStage.name}
{pipelineStage.name} />
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
) : ( ) : (
<> <>
<DropdownMenuHeader <DropdownMenuHeader
data-testid="selected-pipeline-stage" data-testid="selected-pipeline-stage"
endIcon={<IconChevronDown size={theme.icon.size.md} />} EndIcon={IconChevronDown}
onClick={() => setIsProgressSelectionUnfolded(true)} onClick={() => setIsProgressSelectionUnfolded(true)}
> >
{selectedPipelineStage?.name} {selectedPipelineStage?.name}

View File

@@ -29,7 +29,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'name', key: 'name',
name: 'Name', name: 'Name',
icon: <IconBuildingSkyscraper />, Icon: IconBuildingSkyscraper,
size: 180, size: 180,
index: 0, index: 0,
metadata: { metadata: {
@@ -43,7 +43,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'domainName', key: 'domainName',
name: 'URL', name: 'URL',
icon: <IconLink />, Icon: IconLink,
size: 100, size: 100,
index: 1, index: 1,
metadata: { metadata: {
@@ -56,7 +56,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'accountOwner', key: 'accountOwner',
name: 'Account Owner', name: 'Account Owner',
icon: <IconUserCircle />, Icon: IconUserCircle,
size: 150, size: 150,
index: 2, index: 2,
metadata: { metadata: {
@@ -69,7 +69,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'createdAt', key: 'createdAt',
name: 'Creation', name: 'Creation',
icon: <IconCalendarEvent />, Icon: IconCalendarEvent,
size: 150, size: 150,
index: 3, index: 3,
metadata: { metadata: {
@@ -81,7 +81,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'employees', key: 'employees',
name: 'Employees', name: 'Employees',
icon: <IconUsers />, Icon: IconUsers,
size: 150, size: 150,
index: 4, index: 4,
metadata: { metadata: {
@@ -94,7 +94,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'linkedin', key: 'linkedin',
name: 'LinkedIn', name: 'LinkedIn',
icon: <IconBrandLinkedin />, Icon: IconBrandLinkedin,
size: 170, size: 170,
index: 5, index: 5,
metadata: { metadata: {
@@ -107,7 +107,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'address', key: 'address',
name: 'Address', name: 'Address',
icon: <IconMap />, Icon: IconMap,
size: 170, size: 170,
index: 6, index: 6,
metadata: { metadata: {
@@ -120,7 +120,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'idealCustomerProfile', key: 'idealCustomerProfile',
name: 'ICP', name: 'ICP',
icon: <IconTarget />, Icon: IconTarget,
size: 150, size: 150,
index: 7, index: 7,
metadata: { metadata: {
@@ -132,7 +132,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'annualRecurringRevenue', key: 'annualRecurringRevenue',
name: 'ARR', name: 'ARR',
icon: <IconMoneybag />, Icon: IconMoneybag,
size: 150, size: 150,
index: 8, index: 8,
metadata: { metadata: {
@@ -143,7 +143,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
{ {
key: 'xUrl', key: 'xUrl',
name: 'Twitter', name: 'Twitter',
icon: <IconBrandX />, Icon: IconBrandX,
size: 150, size: 150,
index: 9, index: 9,
metadata: { metadata: {

View File

@@ -26,19 +26,19 @@ export function useCompanyTableContextMenuEntries() {
setContextMenuEntries([ setContextMenuEntries([
<ContextMenuEntry <ContextMenuEntry
label="Note" label="Note"
icon={<IconNotes size={16} />} Icon={IconNotes}
onClick={() => handleButtonClick(ActivityType.Note)} onClick={() => handleButtonClick(ActivityType.Note)}
key="note" key="note"
/>, />,
<ContextMenuEntry <ContextMenuEntry
label="Task" label="Task"
icon={<IconCheckbox size={16} />} Icon={IconCheckbox}
onClick={() => handleButtonClick(ActivityType.Task)} onClick={() => handleButtonClick(ActivityType.Task)}
key="task" key="task"
/>, />,
<ContextMenuEntry <ContextMenuEntry
label="Delete" label="Delete"
icon={<IconTrash size={16} />} Icon={IconTrash}
accent="danger" accent="danger"
onClick={() => deleteSelectedCompanies()} onClick={() => deleteSelectedCompanies()}
key="delete" key="delete"

View File

@@ -5,6 +5,7 @@ import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardId
import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition'; import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState'; import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
import { Pipeline } from '~/generated/graphql'; import { Pipeline } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -97,12 +98,22 @@ export function useUpdateCompanyBoard() {
}); });
const newBoardColumns: BoardColumnDefinition[] = const newBoardColumns: BoardColumnDefinition[] =
orderedPipelineStages?.map((pipelineStage) => ({ orderedPipelineStages?.map((pipelineStage) => {
id: pipelineStage.id, if (!isThemeColor(pipelineStage.color)) {
title: pipelineStage.name, console.warn(
colorCode: pipelineStage.color, `Color ${pipelineStage.color} is not recognized in useUpdateCompanyBoard.`,
index: pipelineStage.index ?? 0, );
})); }
return {
id: pipelineStage.id,
title: pipelineStage.name,
colorCode: isThemeColor(pipelineStage.color)
? pipelineStage.color
: undefined,
index: pipelineStage.index ?? 0,
};
});
if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) { if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) {
set(boardColumnsState, newBoardColumns); set(boardColumnsState, newBoardColumns);

View File

@@ -11,7 +11,7 @@ import {
export const fieldsForCompany = [ export const fieldsForCompany = [
{ {
icon: <IconBuildingSkyscraper />, icon: IconBuildingSkyscraper,
label: 'Name', label: 'Name',
key: 'name', key: 'name',
alternateMatches: ['name', 'company name', 'company'], alternateMatches: ['name', 'company name', 'company'],
@@ -21,7 +21,7 @@ export const fieldsForCompany = [
example: 'Tim', example: 'Tim',
}, },
{ {
icon: <IconMail />, icon: IconMail,
label: 'Domain name', label: 'Domain name',
key: 'domainName', key: 'domainName',
alternateMatches: ['domain', 'domain name'], alternateMatches: ['domain', 'domain name'],
@@ -31,7 +31,7 @@ export const fieldsForCompany = [
example: 'apple.dev', example: 'apple.dev',
}, },
{ {
icon: <IconBrandLinkedin />, icon: IconBrandLinkedin,
label: 'Linkedin URL', label: 'Linkedin URL',
key: 'linkedinUrl', key: 'linkedinUrl',
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'], alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
@@ -41,7 +41,7 @@ export const fieldsForCompany = [
example: 'https://www.linkedin.com/in/apple', example: 'https://www.linkedin.com/in/apple',
}, },
{ {
icon: <IconMoneybag />, icon: IconMoneybag,
label: 'ARR', label: 'ARR',
key: 'annualRecurringRevenue', key: 'annualRecurringRevenue',
alternateMatches: [ alternateMatches: [
@@ -64,7 +64,7 @@ export const fieldsForCompany = [
example: '1000000', example: '1000000',
}, },
{ {
icon: <IconTarget />, icon: IconTarget,
label: 'ICP', label: 'ICP',
key: 'idealCustomerProfile', key: 'idealCustomerProfile',
alternateMatches: [ alternateMatches: [
@@ -86,7 +86,7 @@ export const fieldsForCompany = [
example: 'true/false', example: 'true/false',
}, },
{ {
icon: <IconBrandX />, icon: IconBrandX,
label: 'x URL', label: 'x URL',
key: 'xUrl', key: 'xUrl',
alternateMatches: ['x', 'twitter', 'twitter url', 'x url'], alternateMatches: ['x', 'twitter', 'twitter url', 'x url'],
@@ -96,7 +96,7 @@ export const fieldsForCompany = [
example: 'https://x.com/tim_cook', example: 'https://x.com/tim_cook',
}, },
{ {
icon: <IconMap />, icon: IconMap,
label: 'Address', label: 'Address',
key: 'address', key: 'address',
fieldType: { fieldType: {
@@ -105,7 +105,7 @@ export const fieldsForCompany = [
example: 'Maple street', example: 'Maple street',
}, },
{ {
icon: <IconUsers />, icon: IconUsers,
label: 'Employees', label: 'Employees',
key: 'employees', key: 'employees',
alternateMatches: ['employees', 'total employees', 'number of employees'], alternateMatches: ['employees', 'total employees', 'number of employees'],

View File

@@ -1,15 +1,14 @@
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react'; import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { IconDotsVertical, IconLinkOff, IconTrash } from '@/ui/icon'; 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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar'; import { Avatar } from '@/users/components/Avatar';
import { import {
@@ -72,10 +71,6 @@ const StyledJobTitle = styled.div`
} }
`; `;
const StyledRemoveOption = styled.div`
color: ${({ theme }) => theme.color.red};
`;
export function PeopleCard({ export function PeopleCard({
person, person,
hasBottomBorder = true, hasBottomBorder = true,
@@ -93,8 +88,6 @@ export function PeopleCard({
placement: 'right-start', placement: 'right-start',
}); });
const theme = useTheme();
useListenClickOutside({ useListenClickOutside({
refs: [refs.floating], refs: [refs.floating],
callback: () => { callback: () => {
@@ -175,14 +168,17 @@ export function PeopleCard({
<StyledDropdownMenuItemsContainer <StyledDropdownMenuItemsContainer
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<DropdownMenuSelectableItem onClick={handleDetachPerson}> <MenuItem
<IconLinkOff size={14} /> onClick={handleDetachPerson}
Detach relation LeftIcon={IconLinkOff}
</DropdownMenuSelectableItem> text="Detach relation"
<DropdownMenuSelectableItem onClick={handleDeletePerson}> />
<IconTrash size={14} color={theme.font.color.danger} /> <MenuItem
<StyledRemoveOption>Delete person</StyledRemoveOption> onClick={handleDeletePerson}
</DropdownMenuSelectableItem> LeftIcon={IconTrash}
text="Delete person"
accent="danger"
/>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
)} )}

View File

@@ -27,7 +27,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'displayName', key: 'displayName',
name: 'People', name: 'People',
icon: <IconUser />, Icon: IconUser,
size: 210, size: 210,
index: 0, index: 0,
metadata: { metadata: {
@@ -43,7 +43,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'email', key: 'email',
name: 'Email', name: 'Email',
icon: <IconMail />, Icon: IconMail,
size: 150, size: 150,
index: 1, index: 1,
metadata: { metadata: {
@@ -55,7 +55,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'company', key: 'company',
name: 'Company', name: 'Company',
icon: <IconBuildingSkyscraper />, Icon: IconBuildingSkyscraper,
size: 150, size: 150,
index: 2, index: 2,
metadata: { metadata: {
@@ -67,7 +67,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'phone', key: 'phone',
name: 'Phone', name: 'Phone',
icon: <IconPhone />, Icon: IconPhone,
size: 150, size: 150,
index: 3, index: 3,
metadata: { metadata: {
@@ -79,7 +79,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'createdAt', key: 'createdAt',
name: 'Creation', name: 'Creation',
icon: <IconCalendarEvent />, Icon: IconCalendarEvent,
size: 150, size: 150,
index: 4, index: 4,
metadata: { metadata: {
@@ -90,7 +90,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'city', key: 'city',
name: 'City', name: 'City',
icon: <IconMap />, Icon: IconMap,
size: 150, size: 150,
index: 5, index: 5,
metadata: { metadata: {
@@ -102,7 +102,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'jobTitle', key: 'jobTitle',
name: 'Job title', name: 'Job title',
icon: <IconBriefcase />, Icon: IconBriefcase,
size: 150, size: 150,
index: 6, index: 6,
metadata: { metadata: {
@@ -114,7 +114,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'linkedin', key: 'linkedin',
name: 'LinkedIn', name: 'LinkedIn',
icon: <IconBrandLinkedin />, Icon: IconBrandLinkedin,
size: 150, size: 150,
index: 7, index: 7,
metadata: { metadata: {
@@ -126,7 +126,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
{ {
key: 'x', key: 'x',
name: 'Twitter', name: 'Twitter',
icon: <IconBrandX />, Icon: IconBrandX,
size: 150, size: 150,
index: 8, index: 8,
metadata: { metadata: {

View File

@@ -60,19 +60,19 @@ export function usePersonTableContextMenuEntries() {
setContextMenuEntries([ setContextMenuEntries([
<ContextMenuEntry <ContextMenuEntry
label="Note" label="Note"
icon={<IconNotes size={16} />} Icon={IconNotes}
onClick={() => handleActivityClick(ActivityType.Note)} onClick={() => handleActivityClick(ActivityType.Note)}
key="note" key="note"
/>, />,
<ContextMenuEntry <ContextMenuEntry
label="Task" label="Task"
icon={<IconCheckbox size={16} />} Icon={IconCheckbox}
onClick={() => handleActivityClick(ActivityType.Task)} onClick={() => handleActivityClick(ActivityType.Task)}
key="task" key="task"
/>, />,
<ContextMenuEntry <ContextMenuEntry
label="Delete" label="Delete"
icon={<IconTrash size={16} />} Icon={IconTrash}
accent="danger" accent="danger"
onClick={handleDeleteClick} onClick={handleDeleteClick}
key="delete" key="delete"

View File

@@ -1,5 +1,6 @@
import { isValidPhoneNumber } from 'libphonenumber-js'; import { isValidPhoneNumber } from 'libphonenumber-js';
import { Fields } from '@/spreadsheet-import/types';
import { import {
IconBrandLinkedin, IconBrandLinkedin,
IconBrandX, IconBrandX,
@@ -11,7 +12,7 @@ import {
export const fieldsForPerson = [ export const fieldsForPerson = [
{ {
icon: <IconUser />, icon: IconUser,
label: 'Firstname', label: 'Firstname',
key: 'firstName', key: 'firstName',
alternateMatches: ['first name', 'first', 'firstname'], alternateMatches: ['first name', 'first', 'firstname'],
@@ -21,7 +22,7 @@ export const fieldsForPerson = [
example: 'Tim', example: 'Tim',
}, },
{ {
icon: <IconUser />, icon: IconUser,
label: 'Lastname', label: 'Lastname',
key: 'lastName', key: 'lastName',
alternateMatches: ['last name', 'last', 'lastname'], alternateMatches: ['last name', 'last', 'lastname'],
@@ -31,7 +32,7 @@ export const fieldsForPerson = [
example: 'Cook', example: 'Cook',
}, },
{ {
icon: <IconMail />, icon: IconMail,
label: 'Email', label: 'Email',
key: 'email', key: 'email',
alternateMatches: ['email', 'mail'], alternateMatches: ['email', 'mail'],
@@ -41,7 +42,7 @@ export const fieldsForPerson = [
example: 'tim@apple.dev', example: 'tim@apple.dev',
}, },
{ {
icon: <IconBrandLinkedin />, icon: IconBrandLinkedin,
label: 'Linkedin URL', label: 'Linkedin URL',
key: 'linkedinUrl', key: 'linkedinUrl',
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'], alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
@@ -51,7 +52,7 @@ export const fieldsForPerson = [
example: 'https://www.linkedin.com/in/timcook', example: 'https://www.linkedin.com/in/timcook',
}, },
{ {
icon: <IconBrandX />, icon: IconBrandX,
label: 'X URL', label: 'X URL',
key: 'xUrl', key: 'xUrl',
alternateMatches: ['x', 'x url'], alternateMatches: ['x', 'x url'],
@@ -61,7 +62,7 @@ export const fieldsForPerson = [
example: 'https://x.com/tim_cook', example: 'https://x.com/tim_cook',
}, },
{ {
icon: <IconBriefcase />, icon: IconBriefcase,
label: 'Job title', label: 'Job title',
key: 'jobTitle', key: 'jobTitle',
alternateMatches: ['job', 'job title'], alternateMatches: ['job', 'job title'],
@@ -71,7 +72,7 @@ export const fieldsForPerson = [
example: 'CEO', example: 'CEO',
}, },
{ {
icon: <IconBriefcase />, icon: IconBriefcase,
label: 'Phone', label: 'Phone',
key: 'phone', key: 'phone',
fieldType: { fieldType: {
@@ -88,7 +89,7 @@ export const fieldsForPerson = [
], ],
}, },
{ {
icon: <IconMap />, icon: IconMap,
label: 'City', label: 'City',
key: 'city', key: 'city',
fieldType: { fieldType: {
@@ -96,4 +97,4 @@ export const fieldsForPerson = [
}, },
example: 'Seattle', example: 'Seattle',
}, },
] as const; ] as Fields<string>;

View File

@@ -19,7 +19,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
{ {
key: 'closeDate', key: 'closeDate',
name: 'Close Date', name: 'Close Date',
icon: <IconCalendarEvent />, Icon: IconCalendarEvent,
metadata: { metadata: {
type: 'date', type: 'date',
fieldName: 'closeDate', fieldName: 'closeDate',
@@ -29,7 +29,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
{ {
key: 'amount', key: 'amount',
name: 'Amount', name: 'Amount',
icon: <IconCurrencyDollar />, Icon: IconCurrencyDollar,
metadata: { metadata: {
type: 'number', type: 'number',
fieldName: 'amount', fieldName: 'amount',
@@ -39,7 +39,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
{ {
key: 'probability', key: 'probability',
name: 'Probability', name: 'Probability',
icon: <IconProgressCheck />, Icon: IconProgressCheck,
metadata: { metadata: {
type: 'probability', type: 'probability',
fieldName: 'probability', fieldName: 'probability',
@@ -49,7 +49,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
{ {
key: 'pointOfContact', key: 'pointOfContact',
name: 'Point of Contact', name: 'Point of Contact',
icon: <IconUser />, Icon: IconUser,
metadata: { metadata: {
type: 'relation', type: 'relation',
fieldName: 'pointOfContact', fieldName: 'pointOfContact',

View File

@@ -22,7 +22,7 @@ export const usePipelineStages = () => {
return createPipelineStageMutation({ return createPipelineStageMutation({
variables: { variables: {
data: { data: {
color: boardColumn.colorCode, color: boardColumn.colorCode ?? 'gray',
id: boardColumn.id, id: boardColumn.id,
index: boardColumn.index, index: boardColumn.index,
name: boardColumn.title, name: boardColumn.title,

View File

@@ -14,44 +14,15 @@ import { ReadonlyDeep } from 'type-fest';
import type { SelectOption } from '@/spreadsheet-import/types'; import type { SelectOption } from '@/spreadsheet-import/types';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; 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 { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; 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 { AppTooltip } from '@/ui/tooltip/AppTooltip';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useUpdateEffect } from '~/hooks/useUpdateEffect'; 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` const StyledFloatingDropdown = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex}; z-index: ${({ theme }) => theme.lastLayerZIndex};
`; `;
@@ -69,11 +40,9 @@ export const MatchColumnSelect = ({
value, value,
options: initialOptions, options: initialOptions,
placeholder, placeholder,
name,
}: Props) => { }: Props) => {
const theme = useTheme(); const theme = useTheme();
const dropdownItemRef = useRef<HTMLDivElement>(null);
const dropdownContainerRef = useRef<HTMLDivElement>(null); const dropdownContainerRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -123,16 +92,6 @@ export const MatchColumnSelect = ({
setIsOpen(false); setIsOpen(false);
} }
function renderIcon(icon: ReadonlyDeep<React.ReactNode>) {
if (icon && React.isValidElement(icon)) {
return React.cloneElement<TablerIconsProps>(icon as any, {
size: 16,
color: theme.font.color.primary,
});
}
return null;
}
useListenClickOutside({ useListenClickOutside({
refs: [dropdownContainerRef], refs: [dropdownContainerRef],
callback: () => { callback: () => {
@@ -146,28 +105,20 @@ export const MatchColumnSelect = ({
return ( return (
<> <>
<StyledDropdownItem <div ref={refs.setReference}>
id={name} <MenuItem
ref={(node) => { LeftIcon={value?.icon}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment onClick={handleDropdownItemClick}
// @ts-expect-error text={value?.label ?? placeholder ?? ''}
dropdownItemRef.current = node; accent={value?.label ? 'default' : 'placeholder'}
refs.setReference(node); />
}} </div>
onClick={handleDropdownItemClick}
>
{renderIcon(value?.icon)}
<StyledDropdownLabel isPlaceholder={!value?.label}>
{value?.label ?? placeholder}
</StyledDropdownLabel>
<IconChevronDown size={16} color={theme.font.color.tertiary} />
</StyledDropdownItem>
{isOpen && {isOpen &&
createPortal( createPortal(
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}> <StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
<StyledDropdownMenu <StyledDropdownMenu
ref={dropdownContainerRef} ref={dropdownContainerRef}
width={dropdownItemRef.current?.clientWidth} width={refs.domReference.current?.clientWidth}
> >
<DropdownMenuInput <DropdownMenuInput
value={searchFilter} value={searchFilter}
@@ -178,18 +129,16 @@ export const MatchColumnSelect = ({
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{options?.map((option) => ( {options?.map((option) => (
<> <>
<DropdownMenuSelectableItem <MenuItemSelect
id={option.value}
key={option.label} key={option.label}
selected={value?.label === option.label} selected={value?.label === option.label}
onClick={() => handleChange(option)} onClick={() => handleChange(option)}
disabled={ disabled={
option.disabled && value?.value !== option.value option.disabled && value?.value !== option.value
} }
> LeftIcon={option?.icon}
{renderIcon(option?.icon)} text={option.label}
{option.label} />
</DropdownMenuSelectableItem>
{option.disabled && {option.disabled &&
value?.value !== option.value && value?.value !== option.value &&
createPortal( createPortal(
@@ -204,9 +153,7 @@ export const MatchColumnSelect = ({
)} )}
</> </>
))} ))}
{options?.length === 0 && ( {options?.length === 0 && <MenuItem text="No result" />}
<DropdownMenuItem>No result</DropdownMenuItem>
)}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
</StyledFloatingDropdown>, </StyledFloatingDropdown>,

View File

@@ -109,7 +109,7 @@ export const TemplateColumn = <T extends string>({
}); });
const selectOptions = [ const selectOptions = [
{ {
icon: <IconForbid />, icon: IconForbid,
value: 'do-not-import', value: 'do-not-import',
label: 'Do not import', label: 'Do not import',
}, },

View File

@@ -134,7 +134,7 @@ export const generateColumns = <T extends string>(
value={ value={
value value
? ({ ? ({
icon: null, icon: undefined,
...value, ...value,
} as const) } as const)
: value : value

View File

@@ -1,5 +1,5 @@
import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport'; 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 = [ const fields = [
{ {
@@ -85,7 +85,7 @@ const fields = [
}, },
example: 'true', example: 'true',
}, },
] as const; ] as Fields<string>;
const mockComponentBehaviourForTypes = <T extends string>( const mockComponentBehaviourForTypes = <T extends string>(
props: SpreadsheetOptions<T>, props: SpreadsheetOptions<T>,

View File

@@ -3,6 +3,7 @@ import { ReadonlyDeep } from 'type-fest';
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep'; import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow'; import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow';
import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types'; import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types';
import { IconComponent } from '@/ui/icon/types/IconComponent';
export type SpreadsheetOptions<Keys extends string> = { export type SpreadsheetOptions<Keys extends string> = {
// Is modal visible. // Is modal visible.
@@ -65,7 +66,7 @@ export type Fields<T extends string> = ReadonlyDeep<Field<T>[]>;
export type Field<T extends string> = { export type Field<T extends string> = {
// Icon // Icon
icon: React.ReactNode; icon: IconComponent | null | undefined;
// UI-facing field label // UI-facing field label
label: string; label: string;
// Field's unique identifier // Field's unique identifier
@@ -96,7 +97,7 @@ export type Select = {
export type SelectOption = { export type SelectOption = {
// Icon // Icon
icon?: React.ReactNode; icon?: IconComponent | null;
// UI-facing option label // UI-facing option label
label: string; label: string;
// Field entry matching criteria as well as select output // Field entry matching criteria as well as select output

View File

@@ -2,6 +2,7 @@ import React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Tag } from '@/ui/tag/components/Tag'; import { Tag } from '@/ui/tag/components/Tag';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
@@ -52,7 +53,7 @@ const StyledNumChildren = styled.div`
`; `;
export type BoardColumnProps = { export type BoardColumnProps = {
color: string; color?: ThemeColor;
title: string; title: string;
onDelete?: (id: string) => void; onDelete?: (id: string) => void;
onTitleEdit: (title: string, color: string) => void; onTitleEdit: (title: string, color: string) => void;
@@ -97,7 +98,7 @@ export function BoardColumn({
return ( return (
<StyledColumn isFirstColumn={isFirstColumn}> <StyledColumn isFirstColumn={isFirstColumn}>
<StyledHeader> <StyledHeader>
<Tag onClick={handleTitleClick} color={color} text={title} /> <Tag onClick={handleTitleClick} color={color ?? 'gray'} text={title} />
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>} {!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
<StyledNumChildren>{numChildren}</StyledNumChildren> <StyledNumChildren>{numChildren}</StyledNumChildren>
</StyledHeader> </StyledHeader>
@@ -107,7 +108,7 @@ export function BoardColumn({
onDelete={onDelete} onDelete={onDelete}
onTitleEdit={onTitleEdit} onTitleEdit={onTitleEdit}
title={title} title={title}
color={color} color={color ?? 'gray'}
stageId={stageId} stageId={stageId}
/> />
)} )}

View File

@@ -1,9 +1,10 @@
import { ChangeEvent, useState } from 'react'; import { ChangeEvent, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; 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 { textInputStyle } from '@/ui/theme/constants/effects';
import { debounce } from '~/utils/debounce'; import { debounce } from '~/utils/debounce';
@@ -32,21 +33,15 @@ export type BoardColumnEditTitleMenuProps = {
onClose: () => void; onClose: () => void;
title: string; title: string;
onTitleEdit: (title: string, color: string) => void; onTitleEdit: (title: string, color: string) => void;
color: string; color: ThemeColor;
}; };
const StyledColorSample = styled.div<{ colorName: string }>` type ColumnColorOption = {
background-color: ${({ theme, colorName }) => name: string;
theme.tag.background[colorName]}; id: ThemeColor;
border: 1px solid };
${({ theme, colorName }) =>
theme.color[colorName as keyof typeof theme.color]};
border-radius: ${({ theme }) => theme.border.radius.sm};
height: 12px;
width: 12px;
`;
export const COLOR_OPTIONS = [ export const COLUMN_COLOR_OPTIONS: ColumnColorOption[] = [
{ name: 'Green', id: 'green' }, { name: 'Green', id: 'green' },
{ name: 'Turquoise', id: 'turquoise' }, { name: 'Turquoise', id: 'turquoise' },
{ name: 'Sky', id: 'sky' }, { name: 'Sky', id: 'sky' },
@@ -85,18 +80,17 @@ export function BoardColumnEditTitleMenu({
/> />
</StyledEditTitleContainer> </StyledEditTitleContainer>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
{COLOR_OPTIONS.map((colorOption) => ( {COLUMN_COLOR_OPTIONS.map((colorOption) => (
<DropdownMenuSelectableItem <MenuItemSelectColor
key={colorOption.name} key={colorOption.name}
onClick={() => { onClick={() => {
onTitleEdit(title, colorOption.id); onTitleEdit(title, colorOption.id);
onClose(); onClose();
}} }}
color={colorOption.id}
selected={colorOption.id === color} selected={colorOption.id === color}
> text={colorOption.name}
<StyledColorSample colorName={colorOption.id} /> />
{colorOption.name}
</DropdownMenuSelectableItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
); );

View File

@@ -5,7 +5,6 @@ import { Key } from 'ts-key-enum';
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress'; import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery'; import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { IconPencil, IconPlus, IconTrash } from '@/ui/icon'; 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 { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; 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 { 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 { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@@ -32,7 +32,7 @@ const StyledMenuContainer = styled.div`
`; `;
type OwnProps = { type OwnProps = {
color: string; color: ThemeColor;
onClose: () => void; onClose: () => void;
onDelete?: (id: string) => void; onDelete?: (id: string) => void;
onTitleEdit: (title: string, color: string) => void; onTitleEdit: (title: string, color: string) => void;
@@ -52,7 +52,7 @@ export function BoardColumnMenu({
}: OwnProps) { }: OwnProps) {
const [currentMenu, setCurrentMenu] = useState('actions'); const [currentMenu, setCurrentMenu] = useState('actions');
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState); const [, setBoardColumns] = useRecoilState(boardColumnsState);
const boardColumnMenuRef = useRef(null); const boardColumnMenuRef = useRef(null);
@@ -130,21 +130,21 @@ export function BoardColumnMenu({
<StyledDropdownMenu> <StyledDropdownMenu>
{currentMenu === 'actions' && ( {currentMenu === 'actions' && (
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
<DropdownMenuSelectableItem onClick={() => setMenu('title')}> <MenuItem
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} /> onClick={() => setMenu('title')}
Rename LeftIcon={IconPencil}
</DropdownMenuSelectableItem> text="Rename"
<DropdownMenuSelectableItem />
disabled={boardColumns.length <= 1} <MenuItem
onClick={handleDelete} onClick={handleDelete}
> LeftIcon={IconTrash}
<IconTrash size={icon.size.md} stroke={icon.stroke.sm} /> text="Delete"
Delete />
</DropdownMenuSelectableItem> <MenuItem
<DropdownMenuSelectableItem onClick={() => setMenu('add')}> onClick={() => setMenu('add')}
<IconPlus size={icon.size.md} stroke={icon.stroke.sm} /> LeftIcon={IconPlus}
New opportunity text="New opportunity"
</DropdownMenuSelectableItem> />
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
)} )}
{currentMenu === 'title' && ( {currentMenu === 'title' && (

View File

@@ -7,18 +7,19 @@ import { v4 } from 'uuid';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { import {
IconChevronLeft, IconChevronLeft,
IconChevronRight,
IconLayoutKanban, IconLayoutKanban,
IconPlus, IconPlus,
IconSettings, IconSettings,
} from '@/ui/icon'; } 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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
@@ -35,16 +36,18 @@ const StyledIconSettings = styled(IconSettings)`
margin-right: ${({ theme }) => theme.spacing(1)}; margin-right: ${({ theme }) => theme.spacing(1)};
`; `;
const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
margin-left: auto;
`;
enum BoardOptionsMenu { enum BoardOptionsMenu {
StageCreation = 'StageCreation', StageCreation = 'StageCreation',
Stages = 'Stages', Stages = 'Stages',
} }
type ColumnForCreate = {
id: string;
colorCode: ThemeColor;
index: number;
title: string;
};
export function BoardOptionsDropdownContent({ export function BoardOptionsDropdownContent({
customHotkeyScope, customHotkeyScope,
onStageAdd, onStageAdd,
@@ -68,7 +71,7 @@ export function BoardOptionsDropdownContent({
) )
return; return;
const columnToCreate = { const columnToCreate: ColumnForCreate = {
id: v4(), id: v4(),
colorCode: 'gray', colorCode: 'gray',
index: boardColumns.length, index: boardColumns.length,
@@ -113,32 +116,26 @@ export function BoardOptionsDropdownContent({
</DropdownMenuHeader> </DropdownMenuHeader>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
<DropdownMenuItem <MenuItemNavigate
onClick={() => setCurrentMenu(BoardOptionsMenu.Stages)} onClick={() => setCurrentMenu(BoardOptionsMenu.Stages)}
> LeftIcon={IconLayoutKanban}
<IconLayoutKanban size={theme.icon.size.md} /> text="Stages"
Stages />
<StyledIconChevronRight size={theme.icon.size.sm} />
</DropdownMenuItem>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</> </>
)} )}
{currentMenu === BoardOptionsMenu.Stages && ( {currentMenu === BoardOptionsMenu.Stages && (
<> <>
<DropdownMenuHeader <DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
onClick={resetMenu}
>
Stages Stages
</DropdownMenuHeader> </DropdownMenuHeader>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
<DropdownMenuItem <MenuItem
onClick={() => setCurrentMenu(BoardOptionsMenu.StageCreation)} onClick={() => setCurrentMenu(BoardOptionsMenu.StageCreation)}
> LeftIcon={IconPlus}
<IconPlus size={theme.icon.size.md} /> text="Add stage"
Add stage />
</DropdownMenuItem>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</> </>
)} )}

View File

@@ -4,7 +4,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { import {
BoardColumnEditTitleMenu, BoardColumnEditTitleMenu,
COLOR_OPTIONS, COLUMN_COLOR_OPTIONS,
} from '../BoardColumnEditTitleMenu'; } from '../BoardColumnEditTitleMenu';
const meta: Meta<typeof BoardColumnEditTitleMenu> = { const meta: Meta<typeof BoardColumnEditTitleMenu> = {
@@ -14,7 +14,7 @@ const meta: Meta<typeof BoardColumnEditTitleMenu> = {
argTypes: { argTypes: {
color: { color: {
control: 'select', control: 'select',
options: COLOR_OPTIONS.map(({ id }) => id), options: COLUMN_COLOR_OPTIONS.map(({ id }) => id),
}, },
}, },
args: { color: 'green', title: 'Column title' }, args: { color: 'green', title: 'Column title' },

View File

@@ -16,7 +16,7 @@ export function useBoardContextMenuEntries() {
setContextMenuEntries([ setContextMenuEntries([
<ContextMenuEntry <ContextMenuEntry
label="Delete" label="Delete"
icon={<IconTrash size={16} />} Icon={IconTrash}
accent="danger" accent="danger"
onClick={() => deleteSelectedBoardCards()} onClick={() => deleteSelectedBoardCards()}
key="delete" key="delete"

View File

@@ -1,6 +1,8 @@
import { ThemeColor } from '@/ui/theme/constants/colors';
export type BoardColumnDefinition = { export type BoardColumnDefinition = {
id: string; id: string;
title: string; title: string;
index: number; index: number;
colorCode: string; colorCode?: ThemeColor;
}; };

View File

@@ -1,32 +1,22 @@
import { ReactNode } from 'react'; import { IconComponent } from '@/ui/icon/types/IconComponent';
import styled from '@emotion/styled'; import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; type ContextMenuEntryAccent = 'default' | 'danger';
type ContextMenuEntryAccent = 'regular' | 'danger';
type OwnProps = { type OwnProps = {
icon: ReactNode; Icon: IconComponent;
label: string; label: string;
accent?: ContextMenuEntryAccent; accent?: ContextMenuEntryAccent;
onClick: () => void; onClick: () => void;
}; };
const StyledButtonLabel = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
margin-left: ${({ theme }) => theme.spacing(2)};
`;
export function ContextMenuEntry({ export function ContextMenuEntry({
label, label,
icon, Icon,
accent = 'regular', accent = 'default',
onClick, onClick,
}: OwnProps) { }: OwnProps) {
return ( return (
<DropdownMenuItem onClick={onClick} accent={accent}> <MenuItem LeftIcon={Icon} onClick={onClick} accent={accent} text={label} />
{icon}
<StyledButtonLabel>{label}</StyledButtonLabel>
</DropdownMenuItem>
); );
} }

View File

@@ -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<Props>) {
function handleClick() {
onChange?.(!checked);
}
return (
<StyledDropdownMenuCheckableItemContainer onClick={handleClick}>
<StyledLeftContainer>
<Checkbox checked={checked} />
<StyledChildrenContainer>{children}</StyledChildrenContainer>
</StyledLeftContainer>
</StyledDropdownMenuCheckableItemContainer>
);
}

View File

@@ -1,6 +1,9 @@
import { ComponentProps, ReactElement } from 'react'; import { ComponentProps } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconComponent } from '@/ui/icon/types/IconComponent';
const StyledHeader = styled.li` const StyledHeader = styled.li`
align-items: center; align-items: center;
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
@@ -40,23 +43,31 @@ const StyledEndIconWrapper = styled(StyledStartIconWrapper)`
`; `;
type DropdownMenuHeaderProps = ComponentProps<'li'> & { type DropdownMenuHeaderProps = ComponentProps<'li'> & {
startIcon?: ReactElement; StartIcon?: IconComponent;
endIcon?: ReactElement; EndIcon?: IconComponent;
}; };
export function DropdownMenuHeader({ export function DropdownMenuHeader({
children, children,
startIcon, StartIcon,
endIcon, EndIcon,
...props ...props
}: DropdownMenuHeaderProps) { }: DropdownMenuHeaderProps) {
const theme = useTheme();
return ( return (
<StyledHeader {...props}> <StyledHeader {...props}>
{startIcon && ( {StartIcon && (
<StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper> <StyledStartIconWrapper>
<StartIcon size={theme.icon.size.md} />
</StyledStartIconWrapper>
)} )}
{children} {children}
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>} {EndIcon && (
<StyledEndIconWrapper>
<EndIcon size={theme.icon.size.md} />
</StyledEndIconWrapper>
)}
</StyledHeader> </StyledHeader>
); );
} }

View File

@@ -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 (
<StyledItem {...props} accent={accent}>
{children}
{actions && (
<StyledActions
className="actions-hover-container"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<FloatingIconButtonGroup size="small">
{actions}
</FloatingIconButtonGroup>
</StyledActions>
)}
</StyledItem>
);
}

View File

@@ -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<Props, 'hovered'>
>`
${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<Pick<Props, 'disabled'>>`
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<Props>) {
const theme = useTheme();
function handleClick(event: React.MouseEvent<HTMLLIElement>) {
if (disabled) {
return;
}
onClick?.(event);
}
useEffect(() => {
if (hovered) {
window.scrollTo({
behavior: 'smooth',
});
}
}, [hovered]);
return (
<StyledDropdownMenuSelectableItemContainer
{...restProps}
onClick={handleClick}
hovered={hovered}
data-testid="dropdown-menu-item"
>
<StyledLeftContainer disabled={disabled}>{children}</StyledLeftContainer>
<StyledRightIcon>
{selected && <IconCheck size={theme.icon.size.md} />}
</StyledRightIcon>
</StyledDropdownMenuSelectableItemContainer>
);
}

View File

@@ -15,5 +15,6 @@ export const StyledDropdownMenuItemsContainer = styled.div<{
overflow-y: auto; overflow-y: auto;
padding: var(--padding); padding: var(--padding);
padding-right: var(--padding);
width: calc(100% - 2 * var(--padding)); width: calc(100% - 2 * var(--padding));
`; `;

View File

@@ -2,17 +2,16 @@ import { useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { IconButton } from '@/ui/button/components/IconButton';
import { IconPlus, IconUser } from '@/ui/icon'; import { IconPlus, IconUser } from '@/ui/icon';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; 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 { Avatar } from '@/users/components/Avatar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
import { DropdownMenuHeader } from '../DropdownMenuHeader'; import { DropdownMenuHeader } from '../DropdownMenuHeader';
import { DropdownMenuInput } from '../DropdownMenuInput'; import { DropdownMenuInput } from '../DropdownMenuInput';
import { DropdownMenuItem } from '../DropdownMenuItem';
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
import { StyledDropdownMenu } from '../StyledDropdownMenu'; import { StyledDropdownMenu } from '../StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
@@ -101,21 +100,22 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
return ( return (
<> <>
{mockSelectArray.map((item) => ( {mockSelectArray.map((item) => (
<DropdownMenuSelectableItem <MenuItemSelectAvatar
key={item.id} key={item.id}
selected={selectedItem === item.id} selected={selectedItem === item.id}
onClick={() => setSelectedItem(item.id)} onClick={() => setSelectedItem(item.id)}
> avatar={
{hasAvatar && ( hasAvatar ? (
<Avatar <Avatar
placeholder="A" placeholder="A"
avatarUrl={item.avatarUrl} avatarUrl={item.avatarUrl}
size="md" size="md"
type="squared" type="squared"
/> />
)} ) : undefined
{item.name} }
</DropdownMenuSelectableItem> text={item.name}
/>
))} ))}
</> </>
); );
@@ -127,28 +127,28 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
return ( return (
<> <>
{mockSelectArray.map((item) => ( {mockSelectArray.map((item) => (
<DropdownMenuCheckableItem <MenuItemMultiSelectAvatar
key={item.id} key={item.id}
id={item.id} selected={selectedItems.includes(item.id)}
checked={selectedItems.includes(item.id)} onSelectChange={(checked) => {
onChange={(checked) => {
if (checked) { if (checked) {
setSelectedItems([...selectedItems, item.id]); setSelectedItems([...selectedItems, item.id]);
} else { } else {
setSelectedItems(selectedItems.filter((id) => id !== item.id)); setSelectedItems(selectedItems.filter((id) => id !== item.id));
} }
}} }}
> avatar={
{hasAvatar && ( hasAvatar ? (
<Avatar <Avatar
placeholder="A" placeholder="A"
avatarUrl={item.avatarUrl} avatarUrl={item.avatarUrl}
size="md" size="md"
type="squared" type="squared"
/> />
)} ) : undefined
{item.name} }
</DropdownMenuCheckableItem> text={item.name}
/>
))} ))}
</> </>
); );
@@ -182,7 +182,7 @@ export const SimpleMenuItem: Story = {
<StyledDropdownMenu {...args}> <StyledDropdownMenu {...args}>
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{mockSelectArray.map(({ name }) => ( {mockSelectArray.map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem> <MenuItem text={name} />
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
@@ -198,14 +198,14 @@ export const WithHeaders: Story = {
<StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader> <StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader>
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{mockSelectArray.slice(0, 3).map(({ name }) => ( {mockSelectArray.slice(0, 3).map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem> <MenuItem text={name} />
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader> <StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader>
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{mockSelectArray.slice(3).map(({ name }) => ( {mockSelectArray.slice(3).map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem> <MenuItem text={name} />
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
@@ -218,10 +218,7 @@ export const WithIcons: Story = {
<StyledDropdownMenu {...args}> <StyledDropdownMenu {...args}>
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{mockSelectArray.map(({ name }) => ( {mockSelectArray.map(({ name }) => (
<DropdownMenuItem> <MenuItem text={name} LeftIcon={IconUser} />
<IconUser size={16} />
{name}
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
@@ -234,15 +231,11 @@ export const WithActions: Story = {
<StyledDropdownMenu {...args}> <StyledDropdownMenu {...args}>
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{mockSelectArray.map(({ name }, index) => ( {mockSelectArray.map(({ name }, index) => (
<DropdownMenuItem <MenuItem
className={index === 0 ? 'hover' : undefined} className={index === 0 ? 'hover' : undefined}
actions={[ iconButtons={[{ Icon: IconUser }, { Icon: IconPlus }]}
<IconButton icon={<IconUser />} />, text={name}
<IconButton icon={<IconPlus />} />, />
]}
>
{name}
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
@@ -273,7 +266,7 @@ export const Search: Story = {
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{mockSelectArray.map(({ name }) => ( {mockSelectArray.map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem> <MenuItem text={name} />
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>

View File

@@ -2,6 +2,7 @@ import { useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useEditableField } from '../hooks/useEditableField'; import { useEditableField } from '../hooks/useEditableField';
@@ -71,7 +72,7 @@ const StyledEditableFieldBaseContainer = styled.div`
`; `;
type OwnProps = { type OwnProps = {
iconLabel?: React.ReactNode; IconLabel?: IconComponent;
label?: string; label?: string;
labelFixedWidth?: number; labelFixedWidth?: number;
useEditButton?: boolean; useEditButton?: boolean;
@@ -87,7 +88,7 @@ type OwnProps = {
}; };
export function EditableField({ export function EditableField({
iconLabel, IconLabel,
label, label,
labelFixedWidth, labelFixedWidth,
useEditButton, useEditButton,
@@ -125,7 +126,11 @@ export function EditableField({
onMouseLeave={handleContainerMouseLeave} onMouseLeave={handleContainerMouseLeave}
> >
<StyledLabelAndIconContainer> <StyledLabelAndIconContainer>
{iconLabel && <StyledIconContainer>{iconLabel}</StyledIconContainer>} {IconLabel && (
<StyledIconContainer>
<IconLabel />
</StyledIconContainer>
)}
{label && ( {label && (
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel> <StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
)} )}

View File

@@ -18,7 +18,7 @@ export function GenericEditableBooleanField() {
return ( return (
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
displayModeContent={<GenericEditableBooleanFieldDisplayMode />} displayModeContent={<GenericEditableBooleanFieldDisplayMode />}
displayModeContentOnly displayModeContentOnly
/> />

View File

@@ -32,7 +32,7 @@ export function GenericEditableDateField() {
return ( return (
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditableDateFieldEditMode />} editModeContent={<GenericEditableDateFieldEditMode />}
displayModeContent={<GenericEditableDateFieldDisplayMode />} displayModeContent={<GenericEditableDateFieldDisplayMode />}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -31,7 +31,7 @@ export function GenericEditableNumberField() {
return ( return (
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditableNumberFieldEditMode />} editModeContent={<GenericEditableNumberFieldEditMode />}
displayModeContent={fieldValue} displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -33,7 +33,7 @@ export function GenericEditablePhoneField() {
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
useEditButton useEditButton
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditablePhoneFieldEditMode />} editModeContent={<GenericEditablePhoneFieldEditMode />}
displayModeContent={<PhoneInputDisplay value={fieldValue} />} displayModeContent={<PhoneInputDisplay value={fieldValue} />}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -38,7 +38,7 @@ export function GenericEditableRelationField() {
customEditHotkeyScope={{ customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditableRelationFieldEditMode />} editModeContent={<GenericEditableRelationFieldEditMode />}
displayModeContent={<GenericEditableRelationFieldDisplayMode />} displayModeContent={<GenericEditableRelationFieldDisplayMode />}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -31,7 +31,7 @@ export function GenericEditableTextField() {
return ( return (
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditableTextFieldEditMode />} editModeContent={<GenericEditableTextFieldEditMode />}
displayModeContent={fieldValue} displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -33,7 +33,7 @@ export function GenericEditableURLField() {
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
useEditButton useEditButton
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
editModeContent={<GenericEditableURLFieldEditMode />} editModeContent={<GenericEditableURLFieldEditMode />}
displayModeContent={<FieldDisplayURL URL={fieldValue} />} displayModeContent={<FieldDisplayURL URL={fieldValue} />}
isDisplayModeContentEmpty={!fieldValue} isDisplayModeContentEmpty={!fieldValue}

View File

@@ -18,7 +18,7 @@ export function ProbabilityEditableField() {
return ( return (
<RecoilScope SpecificContext={FieldRecoilScopeContext}> <RecoilScope SpecificContext={FieldRecoilScopeContext}>
<EditableField <EditableField
iconLabel={currentEditableFieldDefinition.icon} IconLabel={currentEditableFieldDefinition.Icon}
displayModeContent={<ProbabilityEditableFieldEditMode />} displayModeContent={<ProbabilityEditableFieldEditMode />}
displayModeContentOnly displayModeContentOnly
disableHoverEffect disableHoverEffect

View File

@@ -1,9 +1,11 @@
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { FieldMetadata, FieldType } from './FieldMetadata'; import { FieldMetadata, FieldType } from './FieldMetadata';
export type FieldDefinition<T extends FieldMetadata | unknown> = { export type FieldDefinition<T extends FieldMetadata | unknown> = {
key: string; key: string;
name: string; name: string;
icon?: JSX.Element; Icon?: IconComponent;
type: FieldType; type: FieldType;
metadata: T; metadata: T;
}; };

View File

@@ -1,3 +1,4 @@
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
@@ -118,7 +119,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & (
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = { export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
key: string; key: string;
name: string; name: string;
icon?: JSX.Element; Icon?: IconComponent;
isVisible?: boolean; isVisible?: boolean;
metadata: T; metadata: T;
}; };

View File

@@ -1,5 +1,6 @@
import { EditableField } from '@/ui/editable-field/components/EditableField'; import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; 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 { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { parseDate } from '~/utils/date-utils'; import { parseDate } from '~/utils/date-utils';
@@ -7,13 +8,13 @@ import { parseDate } from '~/utils/date-utils';
import { EditableFieldEditModeDate } from './EditableFieldEditModeDate'; import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
type OwnProps = { type OwnProps = {
icon?: React.ReactNode; Icon?: IconComponent;
label?: string; label?: string;
value: string | null | undefined; value: string | null | undefined;
onSubmit?: (newValue: string) => void; 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) { async function handleChange(newValue: string) {
onSubmit?.(newValue); onSubmit?.(newValue);
} }
@@ -25,7 +26,7 @@ export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
<EditableField <EditableField
// onSubmit={handleSubmit} // onSubmit={handleSubmit}
// onCancel={handleCancel} // onCancel={handleCancel}
iconLabel={icon} IconLabel={Icon}
label={label} label={label}
editModeContent={ editModeContent={
<EditableFieldEditModeDate <EditableFieldEditModeDate

View File

@@ -2,19 +2,20 @@ import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField'; import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext'; import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay'; import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit'; import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
type OwnProps = { type OwnProps = {
icon?: React.ReactNode; Icon?: IconComponent;
placeholder?: string; placeholder?: string;
value: string | null | undefined; value: string | null | undefined;
onSubmit?: (newValue: string) => void; onSubmit?: (newValue: string) => void;
}; };
export function PhoneEditableField({ export function PhoneEditableField({
icon, Icon,
placeholder, placeholder,
value, value,
onSubmit, onSubmit,
@@ -44,7 +45,7 @@ export function PhoneEditableField({
<EditableField <EditableField
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCancel={handleCancel} onCancel={handleCancel}
iconLabel={icon} IconLabel={Icon}
editModeContent={ editModeContent={
<TextInputEdit <TextInputEdit
placeholder={placeholder ?? ''} placeholder={placeholder ?? ''}

View File

@@ -10,10 +10,10 @@ const meta: Meta<typeof DateEditableField> = {
component: DateEditableField, component: DateEditableField,
decorators: [ComponentDecorator], decorators: [ComponentDecorator],
argTypes: { argTypes: {
icon: { Icon: {
type: 'boolean', type: 'boolean',
mapping: { mapping: {
true: <IconCalendar />, true: IconCalendar,
false: undefined, false: undefined,
}, },
}, },
@@ -21,7 +21,7 @@ const meta: Meta<typeof DateEditableField> = {
}, },
args: { args: {
value: new Date().toISOString(), value: new Date().toISOString(),
icon: true, Icon: IconCalendar,
}, },
}; };

View File

@@ -10,17 +10,17 @@ const meta: Meta<typeof PhoneEditableField> = {
component: PhoneEditableField, component: PhoneEditableField,
decorators: [ComponentWithRouterDecorator], decorators: [ComponentWithRouterDecorator],
argTypes: { argTypes: {
icon: { Icon: {
type: 'boolean', type: 'boolean',
mapping: { mapping: {
true: <IconPhone />, true: IconPhone,
false: undefined, false: undefined,
}, },
}, },
}, },
args: { args: {
value: '+33714446494', value: '+33714446494',
icon: true, Icon: IconPhone,
placeholder: 'Phone', placeholder: 'Phone',
}, },
}; };

View File

@@ -1,8 +1,8 @@
import { Context } from 'react'; import { Context } from 'react';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; 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 { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@@ -43,8 +43,9 @@ export function FilterDropdownFilterSelect({
return ( return (
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{availableFilters.map((availableFilter, index) => ( {availableFilters.map((availableFilter, index) => (
<DropdownMenuSelectableItem <MenuItem
key={`select-filter-${index}`} key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => { onClick={() => {
setFilterDefinitionUsedInDropdown(availableFilter); setFilterDefinitionUsedInDropdown(availableFilter);
@@ -58,10 +59,9 @@ export function FilterDropdownFilterSelect({
setFilterDropdownSearchInput(''); setFilterDropdownSearchInput('');
}} }}
> LeftIcon={availableFilter.Icon}
{availableFilter.icon} text={availableFilter.label}
{availableFilter.label} />
</DropdownMenuSelectableItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
); );

View File

@@ -1,5 +1,4 @@
import { Context } from 'react'; import { Context } from 'react';
import { useTheme } from '@emotion/react';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { IconChevronDown } from '@/ui/icon'; import { IconChevronDown } from '@/ui/icon';
@@ -14,8 +13,6 @@ export function FilterDropdownOperandButton({
}: { }: {
context: Context<string | null>; context: Context<string | null>;
}) { }) {
const theme = useTheme();
const [selectedOperandInDropdown] = useRecoilScopedState( const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState, selectedOperandInDropdownScopedState,
context, context,
@@ -36,7 +33,7 @@ export function FilterDropdownOperandButton({
return ( return (
<DropdownMenuHeader <DropdownMenuHeader
key={'selected-filter-operand'} key={'selected-filter-operand'}
endIcon={<IconChevronDown size={theme.icon.size.md} />} EndIcon={IconChevronDown}
onClick={() => setIsFilterDropdownOperandSelectUnfolded(true)} onClick={() => setIsFilterDropdownOperandSelectUnfolded(true)}
> >
{getOperandLabel(selectedOperandInDropdown)} {getOperandLabel(selectedOperandInDropdown)}

View File

@@ -1,7 +1,7 @@
import { Context } from 'react'; import { Context } from 'react';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited'; import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
@@ -66,14 +66,13 @@ export function FilterDropdownOperandSelect({
return ( return (
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{operandsForFilterType.map((filterOperand, index) => ( {operandsForFilterType.map((filterOperand, index) => (
<DropdownMenuItem <MenuItem
key={`select-filter-operand-${index}`} key={`select-filter-operand-${index}`}
onClick={() => { onClick={() => {
handleOperangeChange(filterOperand); handleOperangeChange(filterOperand);
}} }}
> text={getOperandLabel(filterOperand)}
{getOperandLabel(filterOperand)} />
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
); );

View File

@@ -158,14 +158,12 @@ function SortAndFilterBar<SortField>({
return ( return (
<SortOrFilterChip <SortOrFilterChip
key={sort.key} key={sort.key}
testId={sort.key}
labelValue={sort.label} labelValue={sort.label}
id={sort.key} Icon={
icon={ sort.order === 'desc'
sort.order === 'desc' ? ( ? IconArrowNarrowDown
<IconArrowNarrowDown size={theme.icon.size.md} /> : IconArrowNarrowUp
) : (
<IconArrowNarrowUp size={theme.icon.size.md} />
)
} }
isSort isSort
onRemove={() => onRemoveSort(sort.key)} onRemove={() => onRemoveSort(sort.key)}
@@ -181,12 +179,12 @@ function SortAndFilterBar<SortField>({
return ( return (
<SortOrFilterChip <SortOrFilterChip
key={filter.key} key={filter.key}
testId={filter.key}
labelKey={filter.label} labelKey={filter.label}
labelValue={`${getOperandLabelShort(filter.operand)} ${ labelValue={`${getOperandLabelShort(filter.operand)} ${
filter.displayValue filter.displayValue
}`} }`}
id={filter.key} Icon={filter.Icon}
icon={filter.icon}
onRemove={() => { onRemove={() => {
removeFilter(filter.key); removeFilter(filter.key);
}} }}

View File

@@ -1,12 +1,10 @@
import { Context, useCallback, useState } from 'react'; import { Context, useCallback, useState } from 'react';
import { useTheme } from '@emotion/react';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { IconChevronDown } from '@/ui/icon'; 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 { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '../types/interface'; import { SelectedSortType, SortType } from '../types/interface';
@@ -30,8 +28,6 @@ export function SortDropdownButton<SortField>({
onSortSelect, onSortSelect,
HotkeyScope, HotkeyScope,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const theme = useTheme();
const [isUnfolded, setIsUnfolded] = useState(false); const [isUnfolded, setIsUnfolded] = useState(false);
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false); const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
const [selectedSortDirection, setSelectedSortDirection] = const [selectedSortDirection, setSelectedSortDirection] =
@@ -74,21 +70,20 @@ export function SortDropdownButton<SortField>({
{isOptionUnfolded ? ( {isOptionUnfolded ? (
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{options.map((option, index) => ( {options.map((option, index) => (
<DropdownMenuSelectableItem <MenuItem
key={index} key={index}
onClick={() => { onClick={() => {
setSelectedSortDirection(option); setSelectedSortDirection(option);
setIsOptionUnfolded(false); setIsOptionUnfolded(false);
}} }}
> text={option === 'asc' ? 'Ascending' : 'Descending'}
{option === 'asc' ? 'Ascending' : 'Descending'} />
</DropdownMenuSelectableItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
) : ( ) : (
<> <>
<DropdownMenuHeader <DropdownMenuHeader
endIcon={<IconChevronDown size={theme.icon.size.md} />} EndIcon={IconChevronDown}
onClick={() => setIsOptionUnfolded(true)} onClick={() => setIsOptionUnfolded(true)}
> >
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'} {selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
@@ -97,13 +92,13 @@ export function SortDropdownButton<SortField>({
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{availableSorts.map((sort, index) => ( {availableSorts.map((sort, index) => (
<DropdownMenuSelectableItem <MenuItem
testId={`select-sort-${index}`}
key={index} key={index}
onClick={() => handleAddSort(sort)} onClick={() => handleAddSort(sort)}
> LeftIcon={sort.Icon}
{sort.icon} text={sort.label}
<OverflowingTextWithTooltip text={sort.label} /> />
</DropdownMenuSelectableItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</> </>

View File

@@ -1,16 +1,16 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconX } from '@/ui/icon/index'; import { IconX } from '@/ui/icon/index';
import { IconComponent } from '@/ui/icon/types/IconComponent';
type OwnProps = { type OwnProps = {
id: string;
labelKey?: string; labelKey?: string;
labelValue: string; labelValue: string;
icon: ReactNode; Icon?: IconComponent;
onRemove: () => void; onRemove: () => void;
isSort?: boolean; isSort?: boolean;
testId?: string;
}; };
type StyledChipProps = { type StyledChipProps = {
@@ -55,20 +55,24 @@ const StyledLabelKey = styled.div`
`; `;
function SortOrFilterChip({ function SortOrFilterChip({
id,
labelKey, labelKey,
labelValue, labelValue,
icon, Icon,
onRemove, onRemove,
isSort, isSort,
testId,
}: OwnProps) { }: OwnProps) {
const theme = useTheme(); const theme = useTheme();
return ( return (
<StyledChip isSort={isSort}> <StyledChip isSort={isSort}>
<StyledIcon>{icon}</StyledIcon> {Icon && (
<StyledIcon>
<Icon />
</StyledIcon>
)}
{labelKey && <StyledLabelKey>{labelKey}</StyledLabelKey>} {labelKey && <StyledLabelKey>{labelKey}</StyledLabelKey>}
{labelValue} {labelValue}
<StyledDelete onClick={onRemove} data-testid={'remove-icon-' + id}> <StyledDelete onClick={onRemove} data-testid={'remove-icon-' + testId}>
<IconX size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} /> <IconX size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
</StyledDelete> </StyledDelete>
</StyledChip> </StyledChip>

View File

@@ -1,9 +1,11 @@
import { IconComponent } from '@/ui/icon/types/IconComponent';
import { FilterType } from './FilterType'; import { FilterType } from './FilterType';
export type FilterDefinition = { export type FilterDefinition = {
key: string; key: string;
label: string; label: string;
icon: JSX.Element; Icon: IconComponent;
type: FilterType; type: FilterType;
entitySelectComponent?: JSX.Element; entitySelectComponent?: JSX.Element;
}; };

View File

@@ -1,11 +1,10 @@
import { ReactNode } from 'react'; import { IconComponent } from '@/ui/icon/types/IconComponent';
import { SortOrder as Order_By } from '~/generated/graphql'; import { SortOrder as Order_By } from '~/generated/graphql';
export type SortType<OrderByTemplate> = { export type SortType<OrderByTemplate> = {
label: string; label: string;
key: string; key: string;
icon?: ReactNode; Icon?: IconComponent;
orderByTemplate?: (order: Order_By) => OrderByTemplate[]; orderByTemplate?: (order: Order_By) => OrderByTemplate[];
}; };

View File

@@ -1,3 +1,3 @@
import { ComponentType } from 'react'; import { FunctionComponent } from 'react';
export type IconComponent = ComponentType<{ size: number }>; export type IconComponent = FunctionComponent<{ size?: number }>;

View File

@@ -1,12 +1,12 @@
import { useRef } from 'react'; import { useRef } from 'react';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; 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 { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { Avatar } from '@/users/components/Avatar'; import { Avatar } from '@/users/components/Avatar';
import { isNonEmptyString } from '~/utils/isNonEmptyString'; import { isNonEmptyString } from '~/utils/isNonEmptyString';
@@ -81,26 +81,25 @@ export function MultipleEntitySelect<
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
{entitiesInDropdown?.map((entity) => ( {entitiesInDropdown?.map((entity) => (
<DropdownMenuCheckableItem <MenuItemMultiSelectAvatar
key={entity.id} key={entity.id}
checked={value[entity.id]} selected={value[entity.id]}
onChange={(newCheckedValue) => onSelectChange={(newCheckedValue) =>
onChange({ ...value, [entity.id]: newCheckedValue }) onChange({ ...value, [entity.id]: newCheckedValue })
} }
> avatar={
<Avatar <Avatar
avatarUrl={entity.avatarUrl} avatarUrl={entity.avatarUrl}
colorId={entity.id} colorId={entity.id}
placeholder={entity.name} placeholder={entity.name}
size="md" size="md"
type={entity.avatarType ?? 'rounded'} type={entity.avatarType ?? 'rounded'}
/> />
{entity.name} }
</DropdownMenuCheckableItem> text={entity.name}
/>
))} ))}
{entitiesInDropdown?.length === 0 && ( {entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
<DropdownMenuItem>No result</DropdownMenuItem>
)}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
); );

View File

@@ -1,12 +1,11 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { useTheme } from '@emotion/react';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { IconPlus } from '@/ui/icon'; import { IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@@ -39,8 +38,6 @@ export function SingleEntitySelect<
}) { }) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const theme = useTheme();
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch(); const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
const showCreateButton = isDefined(onCreate) && searchFilter !== ''; const showCreateButton = isDefined(onCreate) && searchFilter !== '';
@@ -76,10 +73,7 @@ export function SingleEntitySelect<
{showCreateButton && ( {showCreateButton && (
<> <>
<StyledDropdownMenuItemsContainer hasMaxHeight> <StyledDropdownMenuItemsContainer hasMaxHeight>
<DropdownMenuItem onClick={onCreate}> <MenuItem onClick={onCreate} LeftIcon={IconPlus} text="Add New" />
<IconPlus size={theme.icon.size.md} />
Add New
</DropdownMenuItem>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
</> </>

View File

@@ -1,12 +1,10 @@
import { useRef } from 'react'; import { useRef } from 'react';
import { useTheme } from '@emotion/react';
import { Key } from 'ts-key-enum'; 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 { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { IconBuildingSkyscraper, IconUserCircle } from '@/ui/icon'; 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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { Avatar } from '@/users/components/Avatar'; import { Avatar } from '@/users/components/Avatar';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@@ -76,43 +74,44 @@ export function SingleEntitySelectBase<
entitiesInDropdown = entitiesInDropdown.filter((entity) => entitiesInDropdown = entitiesInDropdown.filter((entity) =>
isNonEmptyString(entity.name.trim()), isNonEmptyString(entity.name.trim()),
); );
const theme = useTheme();
const NoUserIcon =
noUser?.entityType === Entity.User
? IconUserCircle
: IconBuildingSkyscraper;
return ( return (
<StyledDropdownMenuItemsContainer ref={containerRef} hasMaxHeight> <StyledDropdownMenuItemsContainer ref={containerRef} hasMaxHeight>
{noUser && ( {noUser && (
<DropdownMenuItem onClick={() => onEntitySelected(noUser)}> <MenuItem
{noUser.entityType === Entity.User ? ( onClick={() => onEntitySelected(noUser)}
<IconUserCircle size={theme.icon.size.md} /> LeftIcon={NoUserIcon}
) : ( text={noUser.name}
<IconBuildingSkyscraper />
size={theme.icon.size.md}
></IconBuildingSkyscraper>
)}
{noUser.name}
</DropdownMenuItem>
)} )}
{entities.loading ? ( {entities.loading ? (
<DropdownMenuSkeletonItem /> <DropdownMenuSkeletonItem />
) : entitiesInDropdown.length === 0 ? ( ) : entitiesInDropdown.length === 0 ? (
<DropdownMenuItem>No result</DropdownMenuItem> <MenuItem text="No result" />
) : ( ) : (
entitiesInDropdown?.map((entity, index) => ( entitiesInDropdown?.map((entity) => (
<DropdownMenuSelectableItem <MenuItemSelectAvatar
key={entity.id} key={entity.id}
testId="menu-item"
selected={entities.selectedEntity?.id === entity.id} selected={entities.selectedEntity?.id === entity.id}
hovered={hoveredIndex === index}
onClick={() => onEntitySelected(entity)} onClick={() => onEntitySelected(entity)}
> text={entity.name}
<Avatar hovered={hoveredIndex === entitiesInDropdown.indexOf(entity)}
avatarUrl={entity.avatarUrl} avatar={
colorId={entity.id} <Avatar
placeholder={entity.name} avatarUrl={entity.avatarUrl}
size="md" colorId={entity.id}
type={entity.avatarType ?? 'rounded'} placeholder={entity.name}
/> size="md"
<OverflowingTextWithTooltip text={entity.name} /> type={entity.avatarType ?? 'rounded'}
</DropdownMenuSelectableItem> />
}
/>
)) ))
)} )}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>

View File

@@ -4,12 +4,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
import { IconButton } from '@/ui/button/components/IconButton'; import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index'; import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { ActivityType } from '~/generated/graphql'; import { ActivityType } from '~/generated/graphql';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@@ -50,20 +50,18 @@ export function ShowPageAddButton({
<StyledDropdownMenuItemsContainer <StyledDropdownMenuItemsContainer
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<DropdownMenuItem <MenuItem
onClick={() => handleSelect(ActivityType.Note)} onClick={() => handleSelect(ActivityType.Note)}
accent="regular" accent="default"
> LeftIcon={IconNotes}
<IconNotes size={16} /> text="Note"
Note />
</DropdownMenuItem> <MenuItem
<DropdownMenuItem onClick={() => handleSelect(ActivityType.Note)}
onClick={() => handleSelect(ActivityType.Task)} accent="default"
accent="regular" LeftIcon={IconCheckbox}
> text="Task"
<IconCheckbox size={16} /> />
Task
</DropdownMenuItem>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledDropdownMenu> </StyledDropdownMenu>
} }

View File

@@ -1,3 +1,4 @@
import { MouseEvent } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton'; import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
@@ -10,24 +11,26 @@ import { MenuItemAccent } from '../types/MenuItemAccent';
export type MenuItemIconButton = { export type MenuItemIconButton = {
Icon: IconComponent; Icon: IconComponent;
onClick: () => void; onClick?: (event: MouseEvent<any>) => void;
}; };
export type MenuItemProps = { export type MenuItemProps = {
LeftIcon?: IconComponent; LeftIcon?: IconComponent | null;
accent: MenuItemAccent; accent?: MenuItemAccent;
text: string; text: string;
iconButtons?: MenuItemIconButton[]; iconButtons?: MenuItemIconButton[];
className: string; className?: string;
testId?: string;
onClick?: () => void; onClick?: () => void;
}; };
export function MenuItem({ export function MenuItem({
LeftIcon, LeftIcon,
accent, accent = 'default',
text, text,
iconButtons, iconButtons,
className, className,
testId,
onClick, onClick,
}: MenuItemProps) { }: MenuItemProps) {
const theme = useTheme(); const theme = useTheme();
@@ -35,8 +38,13 @@ export function MenuItem({
const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0; const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0;
return ( return (
<StyledMenuItemBase onClick={onClick} className={className} accent={accent}> <StyledMenuItemBase
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} /> data-testid={testId ?? undefined}
onClick={onClick}
className={className}
accent={accent}
>
<MenuItemLeftContent LeftIcon={LeftIcon ?? undefined} text={text} />
{showIconButtons && ( {showIconButtons && (
<FloatingIconButtonGroup> <FloatingIconButtonGroup>
{iconButtons?.map(({ Icon, onClick }, index) => ( {iconButtons?.map(({ Icon, onClick }, index) => (

View File

@@ -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 (
<StyledMenuItemBase className={className} onClick={handleOnClick}>
<StyledLeftContentWithCheckboxContainer>
<Checkbox checked={selected} />
<StyledMenuItemLeftContent>
{avatar}
<StyledMenuItemLabel hasLeftIcon={!!avatar}>
{text}
</StyledMenuItemLabel>
</StyledMenuItemLeftContent>
</StyledLeftContentWithCheckboxContainer>
</StyledMenuItemBase>
);
}

View File

@@ -10,7 +10,7 @@ export type MenuItemProps = {
LeftIcon?: IconComponent; LeftIcon?: IconComponent;
text: string; text: string;
onClick?: () => void; onClick?: () => void;
className: string; className?: string;
}; };
export function MenuItemNavigate({ export function MenuItemNavigate({

View File

@@ -7,8 +7,12 @@ import { IconComponent } from '@/ui/icon/types/IconComponent';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent'; import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase'; import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>` export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
${({ theme, selected }) => { selected: boolean;
disabled?: boolean;
hovered?: boolean;
}>`
${({ theme, selected, disabled, hovered }) => {
if (selected) { if (selected) {
return css` return css`
background: ${theme.background.transparent.light}; background: ${theme.background.transparent.light};
@@ -16,16 +20,33 @@ const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>`
background: ${theme.background.transparent.medium}; 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 = { type OwnProps = {
LeftIcon?: IconComponent; LeftIcon: IconComponent | null | undefined;
selected: boolean; selected: boolean;
text: string; text: string;
className: string; className?: string;
onClick?: () => void; onClick?: () => void;
disabled?: boolean;
hovered?: boolean;
}; };
export function MenuItemSelect({ export function MenuItemSelect({
@@ -34,6 +55,8 @@ export function MenuItemSelect({
selected, selected,
className, className,
onClick, onClick,
disabled,
hovered,
}: OwnProps) { }: OwnProps) {
const theme = useTheme(); const theme = useTheme();
@@ -42,6 +65,8 @@ export function MenuItemSelect({
onClick={onClick} onClick={onClick}
className={className} className={className}
selected={selected} selected={selected}
disabled={disabled}
hovered={hovered}
> >
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} /> <MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
{selected && <IconCheck size={theme.icon.size.sm} />} {selected && <IconCheck size={theme.icon.size.sm} />}

View File

@@ -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 (
<StyledMenuItemSelect
onClick={onClick}
className={className}
selected={selected}
disabled={disabled}
hovered={hovered}
data-testid={testId}
>
<StyledMenuItemLeftContent>
{avatar}
<StyledMenuItemLabel hasLeftIcon={!!avatar}>
<OverflowingTextWithTooltip text={text} />
</StyledMenuItemLabel>
</StyledMenuItemLeftContent>
{selected && <IconCheck size={theme.icon.size.sm} />}
</StyledMenuItemSelect>
);
}

View File

@@ -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 (
<StyledMenuItemSelect
onClick={onClick}
className={className}
selected={selected}
disabled={disabled}
hovered={hovered}
>
<StyledMenuItemLeftContent>
<StyledColorSample colorName={color} />
<StyledMenuItemLabel hasLeftIcon={true}>{text}</StyledMenuItemLabel>
</StyledMenuItemLeftContent>
{selected && <IconCheck size={theme.icon.size.sm} />}
</StyledMenuItemSelect>
);
}

View File

@@ -1,6 +1,7 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { IconComponent } from '@/ui/icon/types/IconComponent'; import { IconComponent } from '@/ui/icon/types/IconComponent';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import { import {
StyledMenuItemLabel, StyledMenuItemLabel,
@@ -8,7 +9,7 @@ import {
} from './StyledMenuItemBase'; } from './StyledMenuItemBase';
type OwnProps = { type OwnProps = {
LeftIcon?: IconComponent; LeftIcon: IconComponent | null | undefined;
text: string; text: string;
}; };
@@ -18,7 +19,9 @@ export function MenuItemLeftContent({ LeftIcon, text }: OwnProps) {
return ( return (
<StyledMenuItemLeftContent> <StyledMenuItemLeftContent>
{LeftIcon && <LeftIcon size={theme.icon.size.md} />} {LeftIcon && <LeftIcon size={theme.icon.size.md} />}
<StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>{text}</StyledMenuItemLabel> <StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>
<OverflowingTextWithTooltip text={text} />
</StyledMenuItemLabel>
</StyledMenuItemLeftContent> </StyledMenuItemLeftContent>
); );
} }

View File

@@ -16,9 +16,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
align-items: center; align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-size: ${({ theme }) => theme.font.size.sm}; font-size: ${({ theme }) => theme.font.size.sm};
@@ -29,8 +29,6 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
justify-content: space-between; justify-content: space-between;
padding: var(--vertical-padding) var(--horizontal-padding);
${hoverBackground}; ${hoverBackground};
${({ theme, accent }) => { ${({ theme, accent }) => {
@@ -43,6 +41,11 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
} }
`; `;
} }
case 'placeholder': {
return css`
color: ${theme.font.color.tertiary};
`;
}
case 'default': case 'default':
default: { default: {
return css` return css`
@@ -52,7 +55,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
} }
}} }}
padding: var(--vertical-padding) var(--horizontal-padding);
position: relative; position: relative;
user-select: none; user-select: none;
width: calc(100% - 2 * var(--horizontal-padding)); width: calc(100% - 2 * var(--horizontal-padding));

View File

@@ -1 +1 @@
export type MenuItemAccent = 'default' | 'danger'; export type MenuItemAccent = 'default' | 'danger' | 'placeholder';

View File

@@ -1,9 +1,11 @@
import { ReactNode } from 'react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconComponent } from '@/ui/icon/types/IconComponent';
type OwnProps = { type OwnProps = {
viewName: string; viewName: string;
viewIcon?: ReactNode; ViewIcon?: IconComponent;
}; };
const StyledTitle = styled.div` const StyledTitle = styled.div`
@@ -32,10 +34,13 @@ const StyledText = styled.span`
white-space: nowrap; white-space: nowrap;
`; `;
export function ColumnHead({ viewName, viewIcon }: OwnProps) { export function ColumnHead({ viewName, ViewIcon }: OwnProps) {
const theme = useTheme();
return ( return (
<StyledTitle> <StyledTitle>
<StyledIcon>{viewIcon}</StyledIcon> <StyledIcon>
{ViewIcon && <ViewIcon size={theme.icon.size.md} />}
</StyledIcon>
<StyledText>{viewName}</StyledText> <StyledText>{viewName}</StyledText>
</StyledTitle> </StyledTitle>
); );

View File

@@ -1,13 +1,11 @@
import { cloneElement, type ComponentProps, useCallback, useRef } from 'react'; import { type ComponentProps, useCallback, useRef } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; 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 { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { IconPlus } from '@/ui/icon'; import { IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@@ -31,7 +29,6 @@ export const EntityTableColumnMenu = ({
...props ...props
}: EntityTableColumnMenuProps) => { }: EntityTableColumnMenuProps) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const theme = useTheme();
const hiddenTableColumns = useRecoilScopedValue( const hiddenTableColumns = useRecoilScopedValue(
hiddenTableColumnsScopedSelector, hiddenTableColumnsScopedSelector,
@@ -57,22 +54,17 @@ export const EntityTableColumnMenu = ({
<StyledColumnMenu {...props} ref={ref}> <StyledColumnMenu {...props} ref={ref}>
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{hiddenTableColumns.map((column) => ( {hiddenTableColumns.map((column) => (
<DropdownMenuItem <MenuItem
key={column.key} key={column.key}
actions={[ iconButtons={[
<IconButton {
key={`add-${column.key}`} Icon: IconPlus,
icon={<IconPlus size={theme.icon.size.sm} />} onClick: () => handleAddColumn(column),
onClick={() => handleAddColumn(column)} },
/>,
]} ]}
> LeftIcon={column.Icon}
{column.icon && text={column.name}
cloneElement(column.icon, { />
size: theme.icon.size.md,
})}
{column.name}
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</StyledColumnMenu> </StyledColumnMenu>

View File

@@ -173,7 +173,7 @@ export function EntityTableHeader() {
COLUMN_MIN_WIDTH, COLUMN_MIN_WIDTH,
)} )}
> >
<ColumnHead viewName={column.name} viewIcon={column.icon} /> <ColumnHead viewName={column.name} ViewIcon={column.Icon} />
<StyledResizeHandler <StyledResizeHandler
className="cursor-col-resize" className="cursor-col-resize"
role="separator" role="separator"

View File

@@ -1,35 +1,25 @@
import { type FormEvent, useCallback, useRef, useState } from 'react'; import { type FormEvent, useCallback, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import { useRecoilCallback, useRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'; import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput'; import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState'; import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState'; import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState'; import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import { import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon';
IconChevronLeft, import { MenuItem } from '@/ui/menu-item/components/MenuItem';
IconFileImport,
IconMinus,
IconPlus,
IconTag,
} from '@/ui/icon';
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState'; import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState'; import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector'; import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
@@ -41,10 +31,9 @@ import {
tableViewsByIdState, tableViewsByIdState,
tableViewsState, tableViewsState,
} from '../../states/tableViewsState'; } from '../../states/tableViewsState';
import type { ColumnDefinition } from '../../types/ColumnDefinition';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope'; import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection'; import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection';
type TableOptionsDropdownButtonProps = { type TableOptionsDropdownButtonProps = {
onViewsChange?: (views: TableView[]) => void; onViewsChange?: (views: TableView[]) => void;
@@ -59,8 +48,6 @@ export function TableOptionsDropdownContent({
onViewsChange, onViewsChange,
onImport, onImport,
}: TableOptionsDropdownButtonProps) { }: TableOptionsDropdownButtonProps) {
const theme = useTheme();
const tableScopeId = useContextScopeId(TableRecoilScopeContext); const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const { closeDropdownButton } = useDropdownButton({ key: 'options' }); const { closeDropdownButton } = useDropdownButton({ key: 'options' });
@@ -87,33 +74,6 @@ export function TableOptionsDropdownContent({
TableRecoilScopeContext, TableRecoilScopeContext,
); );
const { handleColumnVisibilityChange } = useTableColumns();
const renderFieldActions = useCallback(
(column: ColumnDefinition<ViewFieldMetadata>) =>
// Do not allow hiding last visible column
!column.isVisible || visibleTableColumns.length > 1
? [
<IconButton
key={`action-${column.key}`}
icon={
column.isVisible ? (
<IconMinus size={theme.icon.size.sm} />
) : (
<IconPlus size={theme.icon.size.sm} />
)
}
onClick={() => handleColumnVisibilityChange(column)}
/>,
]
: undefined,
[
handleColumnVisibilityChange,
theme.icon.size.sm,
visibleTableColumns.length,
],
);
const resetViewEditMode = useCallback(() => { const resetViewEditMode = useCallback(() => {
setTableViewEditMode({ mode: undefined, viewId: undefined }); setTableViewEditMode({ mode: undefined, viewId: undefined });
@@ -232,17 +192,17 @@ export function TableOptionsDropdownContent({
)} )}
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
<DropdownMenuItem <MenuItem
onClick={() => handleSelectOption(Option.Properties)} onClick={() => handleSelectOption(Option.Properties)}
> LeftIcon={IconTag}
<IconTag size={theme.icon.size.md} /> text="Properties"
Properties />
</DropdownMenuItem>
{onImport && ( {onImport && (
<DropdownMenuItem onClick={onImport}> <MenuItem
<IconFileImport size={theme.icon.size.md} /> onClick={onImport}
Import LeftIcon={IconFileImport}
</DropdownMenuItem> text="Import"
/>
)} )}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</> </>
@@ -250,22 +210,20 @@ export function TableOptionsDropdownContent({
{selectedOption === Option.Properties && ( {selectedOption === Option.Properties && (
<> <>
<DropdownMenuHeader <DropdownMenuHeader
startIcon={<IconChevronLeft size={theme.icon.size.md} />} StartIcon={IconChevronLeft}
onClick={resetSelectedOption} onClick={resetSelectedOption}
> >
Properties Properties
</DropdownMenuHeader> </DropdownMenuHeader>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<TableOptionsDropdownSection <TableOptionsDropdownColumnVisibility
renderActions={renderFieldActions}
title="Visible" title="Visible"
columns={visibleTableColumns} columns={visibleTableColumns}
/> />
{hiddenTableColumns.length > 0 && ( {hiddenTableColumns.length > 0 && (
<> <>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<TableOptionsDropdownSection <TableOptionsDropdownColumnVisibility
renderActions={renderFieldActions}
title="Hidden" title="Hidden"
columns={hiddenTableColumns} columns={hiddenTableColumns}
/> />

View File

@@ -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 { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader'; import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField'; 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'; import type { ColumnDefinition } from '../../types/ColumnDefinition';
type TableOptionsDropdownSectionProps = { type OwnProps = {
renderActions: (
column: ColumnDefinition<ViewFieldMetadata>,
) => DropdownMenuItemProps['actions'];
title: string; title: string;
columns: ColumnDefinition<ViewFieldMetadata>[]; columns: ColumnDefinition<ViewFieldMetadata>[];
}; };
export function TableOptionsDropdownSection({ export function TableOptionsDropdownColumnVisibility({
renderActions,
title, title,
columns, columns,
}: TableOptionsDropdownSectionProps) { }: OwnProps) {
const theme = useTheme(); const { handleColumnVisibilityChange } = useTableColumns();
return ( return (
<> <>
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader> <StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{columns.map((column) => ( {columns.map((column) => (
<DropdownMenuItem key={column.key} actions={renderActions(column)}> <MenuItem
{column.icon && key={column.key}
cloneElement(column.icon, { LeftIcon={column.Icon}
size: theme.icon.size.md, iconButtons={[
})} {
{column.name} Icon: column.isVisible ? IconMinus : IconPlus,
</DropdownMenuItem> onClick: () => handleColumnVisibilityChange(column),
},
]}
text={column.name}
/>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</> </>

View File

@@ -1,12 +1,10 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { Button } from '@/ui/button/components/Button'; import { Button } from '@/ui/button/components/Button';
import { ButtonGroup } from '@/ui/button/components/ButtonGroup'; import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer'; import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer'; 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 { canPersistSortsScopedSelector } from '@/ui/filter-n-sort/states/selectors/canPersistSortsScopedSelector';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState'; import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import { IconChevronDown, IconPlus } from '@/ui/icon'; import { IconChevronDown, IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@@ -45,8 +44,6 @@ export const TableUpdateViewButtonGroup = ({
onViewSubmit, onViewSubmit,
HotkeyScope, HotkeyScope,
}: TableUpdateViewButtonGroupProps) => { }: TableUpdateViewButtonGroupProps) => {
const theme = useTheme();
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const tableScopeId = useContextScopeId(TableRecoilScopeContext); const tableScopeId = useContextScopeId(TableRecoilScopeContext);
@@ -153,10 +150,11 @@ export const TableUpdateViewButtonGroup = ({
{isDropdownOpen && ( {isDropdownOpen && (
<DropdownMenuContainer onClose={handleDropdownClose}> <DropdownMenuContainer onClose={handleDropdownClose}>
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
<DropdownMenuItem onClick={handleCreateViewButtonClick}> <MenuItem
<IconPlus size={theme.icon.size.md} /> onClick={handleCreateViewButtonClick}
Create view LeftIcon={IconPlus}
</DropdownMenuItem> text="Create view"
/>
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
</DropdownMenuContainer> </DropdownMenuContainer>
)} )}

View File

@@ -3,8 +3,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; 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 { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
@@ -20,6 +18,7 @@ import {
IconPlus, IconPlus,
IconTrash, IconTrash,
} from '@/ui/icon'; } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { import {
currentTableViewIdState, currentTableViewIdState,
currentTableViewState, currentTableViewState,
@@ -204,37 +203,35 @@ export const TableViewsDropdownButton = ({
> >
<StyledDropdownMenuItemsContainer> <StyledDropdownMenuItemsContainer>
{tableViews.map((view) => ( {tableViews.map((view) => (
<DropdownMenuItem <MenuItem
key={view.id} key={view.id}
actions={[ iconButtons={[
<FloatingIconButton {
key="edit" Icon: IconPencil,
onClick={(event) => handleEditViewButtonClick(event, view.id)} onClick: (event: MouseEvent<HTMLButtonElement>) =>
icon={<IconPencil size={theme.icon.size.sm} />} handleEditViewButtonClick(event, view.id),
/>, },
tableViews.length > 1 ? ( tableViews.length > 1
<FloatingIconButton ? {
key="delete" Icon: IconTrash,
onClick={(event) => onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleDeleteViewButtonClick(event, view.id) handleDeleteViewButtonClick(event, view.id),
} }
icon={<IconTrash size={theme.icon.size.sm} />} : null,
/>
) : null,
].filter(assertNotNull)} ].filter(assertNotNull)}
onClick={() => handleViewSelect(view.id)} onClick={() => handleViewSelect(view.id)}
> LeftIcon={IconList}
<IconList size={theme.icon.size.md} /> text={view.name}
<StyledViewName>{view.name}</StyledViewName> />
</DropdownMenuItem>
))} ))}
</StyledDropdownMenuItemsContainer> </StyledDropdownMenuItemsContainer>
<StyledDropdownMenuSeparator /> <StyledDropdownMenuSeparator />
<StyledBoldDropdownMenuItemsContainer> <StyledBoldDropdownMenuItemsContainer>
<DropdownMenuItem onClick={handleAddViewButtonClick}> <MenuItem
<IconPlus size={theme.icon.size.md} /> onClick={handleAddViewButtonClick}
Add view LeftIcon={IconPlus}
</DropdownMenuItem> text="Add view"
/>
</StyledBoldDropdownMenuItemsContainer> </StyledBoldDropdownMenuItemsContainer>
</DropdownButton> </DropdownButton>
); );

View File

@@ -1,5 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ThemeColor } from '@/ui/theme/constants/colors';
const tagColors = [ const tagColors = [
'green', 'green',
'turquoise', 'turquoise',
@@ -40,7 +42,7 @@ const StyledTag = styled.h3<{
`; `;
export type TagProps = { export type TagProps = {
color: string; color: ThemeColor;
text: string; text: string;
onClick?: () => void; onClick?: () => void;
}; };

View File

@@ -22,8 +22,22 @@ export const grayScale = {
gray0: '#ffffff', gray0: '#ffffff',
}; };
export const color = { export const mainColors = {
yellow: '#ffd338', 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', yellow80: '#2e2a1a',
yellow70: '#453d1e', yellow70: '#453d1e',
yellow60: '#746224', yellow60: '#746224',
@@ -32,7 +46,7 @@ export const color = {
yellow30: '#ffedaf', yellow30: '#ffedaf',
yellow20: '#fff6d7', yellow20: '#fff6d7',
yellow10: '#fffbeb', yellow10: '#fffbeb',
green: '#55ef3c',
green80: '#1d2d1b', green80: '#1d2d1b',
green70: '#23421e', green70: '#23421e',
green60: '#2a5822', green60: '#2a5822',
@@ -41,7 +55,7 @@ export const color = {
green30: '#ccfac5', green30: '#ccfac5',
green20: '#ddfcd8', green20: '#ddfcd8',
green10: '#eefdec', green10: '#eefdec',
turquoise: '#15de8f',
turquoise80: '#172b23', turquoise80: '#172b23',
turquoise70: '#173f2f', turquoise70: '#173f2f',
turquoise60: '#166747', turquoise60: '#166747',
@@ -50,7 +64,7 @@ export const color = {
turquoise30: '#a1f2d2', turquoise30: '#a1f2d2',
turquoise20: '#d0f8e9', turquoise20: '#d0f8e9',
turquoise10: '#e8fcf4', turquoise10: '#e8fcf4',
sky: '#00e0ff',
sky80: '#152b2e', sky80: '#152b2e',
sky70: '#123f45', sky70: '#123f45',
sky60: '#0e6874', sky60: '#0e6874',
@@ -59,7 +73,7 @@ export const color = {
sky30: '#99f3ff', sky30: '#99f3ff',
sky20: '#ccf9ff', sky20: '#ccf9ff',
sky10: '#e5fcff', sky10: '#e5fcff',
blue: '#1961ed',
blue80: '#171e2c', blue80: '#171e2c',
blue70: '#172642', blue70: '#172642',
blue60: '#18356d', blue60: '#18356d',
@@ -68,7 +82,7 @@ export const color = {
blue30: '#a3c0f8', blue30: '#a3c0f8',
blue20: '#d1dffb', blue20: '#d1dffb',
blue10: '#e8effd', blue10: '#e8effd',
purple: '#915ffd',
purple80: '#231e2e', purple80: '#231e2e',
purple70: '#2f2545', purple70: '#2f2545',
purple60: '#483473', purple60: '#483473',
@@ -77,7 +91,7 @@ export const color = {
purple30: '#d3bffe', purple30: '#d3bffe',
purple20: '#e9dfff', purple20: '#e9dfff',
purple10: '#f4efff', purple10: '#f4efff',
pink: '#f54bd0',
pink80: '#2d1c29', pink80: '#2d1c29',
pink70: '#43213c', pink70: '#43213c',
pink60: '#702c61', pink60: '#702c61',
@@ -86,7 +100,7 @@ export const color = {
pink30: '#fbb7ec', pink30: '#fbb7ec',
pink20: '#fddbf6', pink20: '#fddbf6',
pink10: '#feedfa', pink10: '#feedfa',
red: '#f83e3e',
red80: '#2d1b1b', red80: '#2d1b1b',
red70: '#441f1f', red70: '#441f1f',
red60: '#712727', red60: '#712727',
@@ -95,7 +109,7 @@ export const color = {
red30: '#fcb2b2', red30: '#fcb2b2',
red20: '#fed8d8', red20: '#fed8d8',
red10: '#feecec', red10: '#feecec',
orange: '#ff7222',
orange80: '#2e2018', orange80: '#2e2018',
orange70: '#452919', orange70: '#452919',
orange60: '#743b1b', orange60: '#743b1b',
@@ -104,7 +118,7 @@ export const color = {
orange30: '#ffc7a7', orange30: '#ffc7a7',
orange20: '#ffe3d3', orange20: '#ffe3d3',
orange10: '#fff1e9', orange10: '#fff1e9',
gray: grayScale.gray30,
gray80: grayScale.gray70, gray80: grayScale.gray70,
gray70: grayScale.gray65, gray70: grayScale.gray65,
gray60: grayScale.gray55, gray60: grayScale.gray55,
@@ -127,6 +141,11 @@ export const color = {
blueAccent10: '#f5f9fd', blueAccent10: '#f5f9fd',
}; };
export const color = {
...mainColors,
...secondaryColors,
};
export function rgba(hex: string, alpha: number) { export function rgba(hex: string, alpha: number) {
const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(','); const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(',');
return `rgba(${rgb},${alpha})`; return `rgba(${rgb},${alpha})`;

View File

@@ -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);
}

View File

@@ -35,11 +35,7 @@ export const FilterByName: Story = {
const filterButton = await canvas.findByText('Filter'); const filterButton = await canvas.findByText('Filter');
await userEvent.click(filterButton); await userEvent.click(filterButton);
const nameFilterButton = ( const nameFilterButton = await canvas.findByTestId('select-filter-0');
await canvas.findAllByTestId('dropdown-menu-item')
).find((item) => {
return item.textContent === 'Name';
});
assert(nameFilterButton); assert(nameFilterButton);
@@ -70,11 +66,9 @@ export const FilterByAccountOwner: Story = {
const filterButton = await canvas.findByText('Filter'); const filterButton = await canvas.findByText('Filter');
await userEvent.click(filterButton); await userEvent.click(filterButton);
const accountOwnerFilterButton = ( const accountOwnerFilterButton = await canvas.findByTestId(
await canvas.findAllByTestId('dropdown-menu-item') 'select-filter-5',
).find((item) => { );
return item.textContent === 'Account owner';
});
assert(accountOwnerFilterButton); assert(accountOwnerFilterButton);
@@ -89,11 +83,11 @@ export const FilterByAccountOwner: Story = {
await sleep(1000); await sleep(1000);
const charlesChip = ( const charlesChip = (await canvas.findAllByTestId('menu-item')).find(
await canvas.findAllByTestId('dropdown-menu-item') (item) => {
).find((item) => { return item.textContent?.includes('Charles Test');
return item.textContent?.includes('Charles Test'); },
}); );
assert(charlesChip); assert(charlesChip);

View File

@@ -33,9 +33,8 @@ export const SortByName: Story = {
const sortButton = await canvas.findByText('Sort'); const sortButton = await canvas.findByText('Sort');
await userEvent.click(sortButton); await userEvent.click(sortButton);
const nameSortButton = canvas.getByText('Name', { const nameSortButton = await canvas.findByTestId('select-sort-0');
selector: 'li > div > div',
});
await userEvent.click(nameSortButton); await userEvent.click(nameSortButton);
expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument(); expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument();

View File

@@ -8,7 +8,6 @@ import {
IconUsers, IconUsers,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { icon } from '@/ui/theme/constants/icon';
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect'; import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
import { Company } from '~/generated/graphql'; import { Company } from '~/generated/graphql';
@@ -16,39 +15,37 @@ export const companiesFilters: FilterDefinitionByEntity<Company>[] = [
{ {
key: 'name', key: 'name',
label: 'Name', label: 'Name',
icon: ( Icon: IconBuildingSkyscraper,
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
),
type: 'text', type: 'text',
}, },
{ {
key: 'employees', key: 'employees',
label: 'Employees', label: 'Employees',
icon: <IconUsers size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconUsers,
type: 'number', type: 'number',
}, },
{ {
key: 'domainName', key: 'domainName',
label: 'URL', label: 'URL',
icon: <IconLink size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconLink,
type: 'text', type: 'text',
}, },
{ {
key: 'address', key: 'address',
label: 'Address', label: 'Address',
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconMap,
type: 'text', type: 'text',
}, },
{ {
key: 'createdAt', key: 'createdAt',
label: 'Created at', label: 'Created at',
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconCalendarEvent,
type: 'date', type: 'date',
}, },
{ {
key: 'accountOwnerId', key: 'accountOwnerId',
label: 'Account owner', label: 'Account owner',
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconUser,
type: 'entity', type: 'entity',
entitySelectComponent: ( entitySelectComponent: (
<FilterDropdownUserSearchSelect context={TableRecoilScopeContext} /> <FilterDropdownUserSearchSelect context={TableRecoilScopeContext} />

View File

@@ -12,26 +12,26 @@ export const availableSorts: SortType<Companies_Order_By>[] = [
{ {
key: 'name', key: 'name',
label: 'Name', label: 'Name',
icon: <IconBuildingSkyscraper size={16} />, Icon: IconBuildingSkyscraper,
}, },
{ {
key: 'employees', key: 'employees',
label: 'Employees', label: 'Employees',
icon: <IconUsers size={16} />, Icon: IconUsers,
}, },
{ {
key: 'domainName', key: 'domainName',
label: 'Url', label: 'Url',
icon: <IconLink size={16} />, Icon: IconLink,
}, },
{ {
key: 'address', key: 'address',
label: 'Address', label: 'Address',
icon: <IconMap size={16} />, Icon: IconMap,
}, },
{ {
key: 'createdAt', key: 'createdAt',
label: 'Creation', label: 'Creation',
icon: <IconCalendarEvent size={16} />, Icon: IconCalendarEvent,
}, },
]; ];

View File

@@ -23,7 +23,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'domainName', key: 'domainName',
name: 'Domain name', name: 'Domain name',
icon: <IconLink />, Icon: IconLink,
type: 'url', type: 'url',
metadata: { metadata: {
fieldName: 'domainName', fieldName: 'domainName',
@@ -33,7 +33,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'accountOwner', key: 'accountOwner',
name: 'Account owner', name: 'Account owner',
icon: <IconUserCircle />, Icon: IconUserCircle,
type: 'relation', type: 'relation',
metadata: { metadata: {
fieldName: 'accountOwner', fieldName: 'accountOwner',
@@ -43,7 +43,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'employees', key: 'employees',
name: 'Employees', name: 'Employees',
icon: <IconUsers />, Icon: IconUsers,
type: 'number', type: 'number',
metadata: { metadata: {
fieldName: 'employees', fieldName: 'employees',
@@ -53,7 +53,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'address', key: 'address',
name: 'Address', name: 'Address',
icon: <IconMap />, Icon: IconMap,
type: 'text', type: 'text',
metadata: { metadata: {
fieldName: 'address', fieldName: 'address',
@@ -63,7 +63,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'idealCustomerProfile', key: 'idealCustomerProfile',
name: 'ICP', name: 'ICP',
icon: <IconTarget />, Icon: IconTarget,
type: 'boolean', type: 'boolean',
metadata: { metadata: {
fieldName: 'idealCustomerProfile', fieldName: 'idealCustomerProfile',
@@ -72,7 +72,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'xUrl', key: 'xUrl',
name: 'Twitter', name: 'Twitter',
icon: <IconBrandX />, Icon: IconBrandX,
type: 'url', type: 'url',
metadata: { metadata: {
fieldName: 'xUrl', fieldName: 'xUrl',
@@ -82,7 +82,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'createdAt', key: 'createdAt',
name: 'Created at', name: 'Created at',
icon: <IconCalendar />, Icon: IconCalendar,
type: 'date', type: 'date',
metadata: { metadata: {
fieldName: 'createdAt', fieldName: 'createdAt',

View File

@@ -7,7 +7,6 @@ import {
IconCurrencyDollar, IconCurrencyDollar,
IconUser, IconUser,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { icon } from '@/ui/theme/constants/icon';
import { PipelineProgress } from '~/generated/graphql'; import { PipelineProgress } from '~/generated/graphql';
import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect'; import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect';
@@ -17,21 +16,19 @@ export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[]
{ {
key: 'amount', key: 'amount',
label: 'Amount', label: 'Amount',
icon: <IconCurrencyDollar size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconCurrencyDollar,
type: 'number', type: 'number',
}, },
{ {
key: 'closeDate', key: 'closeDate',
label: 'Close date', label: 'Close date',
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconCalendarEvent,
type: 'date', type: 'date',
}, },
{ {
key: 'companyId', key: 'companyId',
label: 'Company', label: 'Company',
icon: ( Icon: IconBuildingSkyscraper,
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
),
type: 'entity', type: 'entity',
entitySelectComponent: ( entitySelectComponent: (
<FilterDropdownCompanySearchSelect <FilterDropdownCompanySearchSelect
@@ -42,7 +39,7 @@ export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[]
{ {
key: 'pointOfContactId', key: 'pointOfContactId',
label: 'Point of contact', label: 'Point of contact',
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconUser,
type: 'entity', type: 'entity',
entitySelectComponent: ( entitySelectComponent: (
<FilterDropdownPeopleSearchSelect <FilterDropdownPeopleSearchSelect

View File

@@ -6,16 +6,16 @@ export const opportunitiesSorts = [
{ {
key: 'createdAt', key: 'createdAt',
label: 'Creation', label: 'Creation',
icon: <IconCalendarEvent size={16} />, Icon: IconCalendarEvent,
}, },
{ {
key: 'amount', key: 'amount',
label: 'Amount', label: 'Amount',
icon: <IconCurrencyDollar size={16} />, Icon: IconCurrencyDollar,
}, },
{ {
key: 'closeDate', key: 'closeDate',
label: 'Expected close date', label: 'Expected close date',
icon: <IconCalendarEvent size={16} />, Icon: IconCalendarEvent,
}, },
] satisfies Array<SortType<PipelineProgresses_Order_By>>; ] satisfies Array<SortType<PipelineProgresses_Order_By>>;

View File

@@ -35,11 +35,7 @@ export const Email: Story = {
const filterButton = await canvas.findByText('Filter'); const filterButton = await canvas.findByText('Filter');
await userEvent.click(filterButton); await userEvent.click(filterButton);
const emailFilterButton = ( const emailFilterButton = await canvas.findByTestId('select-filter-2');
await canvas.findAllByTestId('dropdown-menu-item')
).find((item) => {
return item.textContent?.includes('Email');
});
assert(emailFilterButton); assert(emailFilterButton);
@@ -70,11 +66,7 @@ export const CompanyName: Story = {
const filterButton = await canvas.findByText('Filter'); const filterButton = await canvas.findByText('Filter');
await userEvent.click(filterButton); await userEvent.click(filterButton);
const companyFilterButton = ( const companyFilterButton = await canvas.findByTestId('select-filter-3');
await canvas.findAllByTestId('dropdown-menu-item')
).find((item) => {
return item.textContent?.includes('Company');
});
assert(companyFilterButton); assert(companyFilterButton);
@@ -87,7 +79,7 @@ export const CompanyName: Story = {
await sleep(500); await sleep(500);
const qontoChip = (await canvas.findAllByTestId('dropdown-menu-item')).find( const qontoChip = (await canvas.findAllByTestId('menu-item')).find(
(item) => { (item) => {
return item.textContent?.includes('Qonto'); return item.textContent?.includes('Qonto');
}, },

View File

@@ -34,9 +34,7 @@ export const Email: Story = {
const sortButton = await canvas.findByText('Sort'); const sortButton = await canvas.findByText('Sort');
await userEvent.click(sortButton); await userEvent.click(sortButton);
const emailSortButton = canvas.getByText('Email', { const emailSortButton = await canvas.findByTestId('select-sort-2');
selector: 'li > div > div',
});
await userEvent.click(emailSortButton); await userEvent.click(emailSortButton);
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument(); expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
@@ -52,9 +50,7 @@ export const Cancel: Story = {
const sortButton = await canvas.findByText('Sort'); const sortButton = await canvas.findByText('Sort');
await userEvent.click(sortButton); await userEvent.click(sortButton);
const emailSortButton = canvas.getByText('Email', { const emailSortButton = await canvas.findByTestId('select-sort-2');
selector: 'li > div > div',
});
await userEvent.click(emailSortButton); await userEvent.click(emailSortButton);
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument(); expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();

View File

@@ -23,7 +23,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'email', key: 'email',
name: 'Email', name: 'Email',
icon: <IconMail />, Icon: IconMail,
type: 'text', type: 'text',
metadata: { metadata: {
fieldName: 'email', fieldName: 'email',
@@ -33,7 +33,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'company', key: 'company',
name: 'Company', name: 'Company',
icon: <IconBuildingSkyscraper />, Icon: IconBuildingSkyscraper,
type: 'relation', type: 'relation',
metadata: { metadata: {
fieldName: 'company', fieldName: 'company',
@@ -44,7 +44,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'phone', key: 'phone',
name: 'Phone', name: 'Phone',
icon: <IconPhone />, Icon: IconPhone,
type: 'phone', type: 'phone',
metadata: { metadata: {
fieldName: 'phone', fieldName: 'phone',
@@ -54,7 +54,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'jobTitle', key: 'jobTitle',
name: 'Job Title', name: 'Job Title',
icon: <IconBriefcase />, Icon: IconBriefcase,
type: 'text', type: 'text',
metadata: { metadata: {
fieldName: 'jobTitle', fieldName: 'jobTitle',
@@ -64,7 +64,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'city', key: 'city',
name: 'City', name: 'City',
icon: <IconMap />, Icon: IconMap,
type: 'text', type: 'text',
metadata: { metadata: {
fieldName: 'city', fieldName: 'city',
@@ -74,7 +74,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'linkedinUrl', key: 'linkedinUrl',
name: 'Linkedin URL', name: 'Linkedin URL',
icon: <IconBrandLinkedin />, Icon: IconBrandLinkedin,
type: 'url', type: 'url',
metadata: { metadata: {
fieldName: 'linkedinUrl', fieldName: 'linkedinUrl',
@@ -84,7 +84,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'xUrl', key: 'xUrl',
name: 'X URL', name: 'X URL',
icon: <IconBrandX />, Icon: IconBrandX,
type: 'url', type: 'url',
metadata: { metadata: {
fieldName: 'xUrl', fieldName: 'xUrl',
@@ -94,7 +94,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
{ {
key: 'createdAt', key: 'createdAt',
name: 'Created at', name: 'Created at',
icon: <IconCalendar />, Icon: IconCalendar,
type: 'date', type: 'date',
metadata: { metadata: {
fieldName: 'createdAt', fieldName: 'createdAt',

View File

@@ -9,34 +9,31 @@ import {
IconUser, IconUser,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { icon } from '@/ui/theme/constants/icon';
import { Person } from '~/generated/graphql'; import { Person } from '~/generated/graphql';
export const peopleFilters: FilterDefinitionByEntity<Person>[] = [ export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
{ {
key: 'firstName', key: 'firstName',
label: 'First name', label: 'First name',
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconUser,
type: 'text', type: 'text',
}, },
{ {
key: 'lastName', key: 'lastName',
label: 'Last name', label: 'Last name',
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconUser,
type: 'text', type: 'text',
}, },
{ {
key: 'email', key: 'email',
label: 'Email', label: 'Email',
icon: <IconMail size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconMail,
type: 'text', type: 'text',
}, },
{ {
key: 'companyId', key: 'companyId',
label: 'Company', label: 'Company',
icon: ( Icon: IconBuildingSkyscraper,
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
),
type: 'entity', type: 'entity',
entitySelectComponent: ( entitySelectComponent: (
<FilterDropdownCompanySearchSelect context={TableRecoilScopeContext} /> <FilterDropdownCompanySearchSelect context={TableRecoilScopeContext} />
@@ -45,19 +42,19 @@ export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
{ {
key: 'phone', key: 'phone',
label: 'Phone', label: 'Phone',
icon: <IconPhone size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconPhone,
type: 'text', type: 'text',
}, },
{ {
key: 'createdAt', key: 'createdAt',
label: 'Created at', label: 'Created at',
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconCalendarEvent,
type: 'date', type: 'date',
}, },
{ {
key: 'city', key: 'city',
label: 'City', label: 'City',
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />, Icon: IconMap,
type: 'text', type: 'text',
}, },
]; ];

View File

@@ -16,7 +16,7 @@ export const availableSorts: SortType<People_Order_By>[] = [
{ {
key: 'fullname', key: 'fullname',
label: 'People', label: 'People',
icon: <IconUser size={16} />, Icon: IconUser,
orderByTemplate: (order: Order_By) => [ orderByTemplate: (order: Order_By) => [
{ firstName: order }, { firstName: order },
@@ -26,28 +26,27 @@ export const availableSorts: SortType<People_Order_By>[] = [
{ {
key: 'company_name', key: 'company_name',
label: 'Company', label: 'Company',
icon: <IconBuildingSkyscraper size={16} />, Icon: IconBuildingSkyscraper,
orderByTemplate: (order: Order_By) => [{ company: { name: order } }], orderByTemplate: (order: Order_By) => [{ company: { name: order } }],
}, },
{ {
key: 'email', key: 'email',
label: 'Email', label: 'Email',
icon: <IconMail size={16} />, Icon: IconMail,
}, },
{ {
key: 'phone', key: 'phone',
label: 'Phone', label: 'Phone',
icon: <IconPhone size={16} />, Icon: IconPhone,
}, },
{ {
key: 'createdAt', key: 'createdAt',
label: 'Created at', label: 'Created at',
icon: <IconCalendarEvent size={16} />, Icon: IconCalendarEvent,
}, },
{ {
key: 'city', key: 'city',
label: 'City', label: 'City',
icon: <IconMap size={16} />, Icon: IconMap,
}, },
]; ];

View File

@@ -8,7 +8,7 @@ export const tasksFilters: FilterDefinitionByEntity<Activity>[] = [
{ {
key: 'assigneeId', key: 'assigneeId',
label: 'Assignee', label: 'Assignee',
icon: <IconUser />, Icon: IconUser,
type: 'entity', type: 'entity',
entitySelectComponent: ( entitySelectComponent: (
<FilterDropdownUserSearchSelect context={TasksRecoilScopeContext} /> <FilterDropdownUserSearchSelect context={TasksRecoilScopeContext} />