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, 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",
"storybook:dev": "storybook dev -p 6006 -s ../public",
"storybook:test": "test-storybook",
"storybook:test-slow": "test-storybook --maxWorkers=3",
"storybook:build": "storybook build -s public",
"storybook:coverage": "test-storybook --coverage --maxWorkers=3 && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
"graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,44 +14,15 @@ import { ReadonlyDeep } from 'type-fest';
import type { SelectOption } from '@/spreadsheet-import/types';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { IconChevronDown, TablerIconsProps } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect';
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
const StyledDropdownItem = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.background.tertiary};
border-radius: ${({ theme }) => theme.border.radius.sm};
box-sizing: border-box;
display: flex;
flex-direction: row;
height: 32px;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
width: 100%;
&:hover {
background-color: ${({ theme }) => theme.background.quaternary};
}
`;
const StyledDropdownLabel = styled.span<{ isPlaceholder: boolean }>`
color: ${({ theme, isPlaceholder }) =>
isPlaceholder ? theme.font.color.tertiary : theme.font.color.primary};
display: flex;
flex: 1;
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
padding-left: ${({ theme }) => theme.spacing(1)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledFloatingDropdown = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
@@ -69,11 +40,9 @@ export const MatchColumnSelect = ({
value,
options: initialOptions,
placeholder,
name,
}: Props) => {
const theme = useTheme();
const dropdownItemRef = useRef<HTMLDivElement>(null);
const dropdownContainerRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
@@ -123,16 +92,6 @@ export const MatchColumnSelect = ({
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({
refs: [dropdownContainerRef],
callback: () => {
@@ -146,28 +105,20 @@ export const MatchColumnSelect = ({
return (
<>
<StyledDropdownItem
id={name}
ref={(node) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
dropdownItemRef.current = node;
refs.setReference(node);
}}
<div ref={refs.setReference}>
<MenuItem
LeftIcon={value?.icon}
onClick={handleDropdownItemClick}
>
{renderIcon(value?.icon)}
<StyledDropdownLabel isPlaceholder={!value?.label}>
{value?.label ?? placeholder}
</StyledDropdownLabel>
<IconChevronDown size={16} color={theme.font.color.tertiary} />
</StyledDropdownItem>
text={value?.label ?? placeholder ?? ''}
accent={value?.label ? 'default' : 'placeholder'}
/>
</div>
{isOpen &&
createPortal(
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
<StyledDropdownMenu
ref={dropdownContainerRef}
width={dropdownItemRef.current?.clientWidth}
width={refs.domReference.current?.clientWidth}
>
<DropdownMenuInput
value={searchFilter}
@@ -178,18 +129,16 @@ export const MatchColumnSelect = ({
<StyledDropdownMenuItemsContainer hasMaxHeight>
{options?.map((option) => (
<>
<DropdownMenuSelectableItem
id={option.value}
<MenuItemSelect
key={option.label}
selected={value?.label === option.label}
onClick={() => handleChange(option)}
disabled={
option.disabled && value?.value !== option.value
}
>
{renderIcon(option?.icon)}
{option.label}
</DropdownMenuSelectableItem>
LeftIcon={option?.icon}
text={option.label}
/>
{option.disabled &&
value?.value !== option.value &&
createPortal(
@@ -204,9 +153,7 @@ export const MatchColumnSelect = ({
)}
</>
))}
{options?.length === 0 && (
<DropdownMenuItem>No result</DropdownMenuItem>
)}
{options?.length === 0 && <MenuItem text="No result" />}
</StyledDropdownMenuItemsContainer>
</StyledDropdownMenu>
</StyledFloatingDropdown>,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 { IconComponent } from '@/ui/icon/types/IconComponent';
const StyledHeader = styled.li`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
@@ -40,23 +43,31 @@ const StyledEndIconWrapper = styled(StyledStartIconWrapper)`
`;
type DropdownMenuHeaderProps = ComponentProps<'li'> & {
startIcon?: ReactElement;
endIcon?: ReactElement;
StartIcon?: IconComponent;
EndIcon?: IconComponent;
};
export function DropdownMenuHeader({
children,
startIcon,
endIcon,
StartIcon,
EndIcon,
...props
}: DropdownMenuHeaderProps) {
const theme = useTheme();
return (
<StyledHeader {...props}>
{startIcon && (
<StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper>
{StartIcon && (
<StyledStartIconWrapper>
<StartIcon size={theme.icon.size.md} />
</StyledStartIconWrapper>
)}
{children}
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>}
{EndIcon && (
<StyledEndIconWrapper>
<EndIcon size={theme.icon.size.md} />
</StyledEndIconWrapper>
)}
</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;
padding: var(--padding);
padding-right: var(--padding);
width: calc(100% - 2 * var(--padding));
`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,8 +7,12 @@ import { IconComponent } from '@/ui/icon/types/IconComponent';
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>`
${({ theme, selected }) => {
export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
selected: boolean;
disabled?: boolean;
hovered?: boolean;
}>`
${({ theme, selected, disabled, hovered }) => {
if (selected) {
return css`
background: ${theme.background.transparent.light};
@@ -16,16 +20,33 @@ const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>`
background: ${theme.background.transparent.medium};
}
`;
} else if (disabled) {
return css`
background: inherit;
&:hover {
background: inherit;
}
color: ${theme.font.color.tertiary};
cursor: default;
`;
} else if (hovered) {
return css`
background: ${theme.background.transparent.light};
`;
}
}}
`;
type OwnProps = {
LeftIcon?: IconComponent;
LeftIcon: IconComponent | null | undefined;
selected: boolean;
text: string;
className: string;
className?: string;
onClick?: () => void;
disabled?: boolean;
hovered?: boolean;
};
export function MenuItemSelect({
@@ -34,6 +55,8 @@ export function MenuItemSelect({
selected,
className,
onClick,
disabled,
hovered,
}: OwnProps) {
const theme = useTheme();
@@ -42,6 +65,8 @@ export function MenuItemSelect({
onClick={onClick}
className={className}
selected={selected}
disabled={disabled}
hovered={hovered}
>
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
{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 { IconComponent } from '@/ui/icon/types/IconComponent';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import {
StyledMenuItemLabel,
@@ -8,7 +9,7 @@ import {
} from './StyledMenuItemBase';
type OwnProps = {
LeftIcon?: IconComponent;
LeftIcon: IconComponent | null | undefined;
text: string;
};
@@ -18,7 +19,9 @@ export function MenuItemLeftContent({ LeftIcon, text }: OwnProps) {
return (
<StyledMenuItemLeftContent>
{LeftIcon && <LeftIcon size={theme.icon.size.md} />}
<StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>{text}</StyledMenuItemLabel>
<StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>
<OverflowingTextWithTooltip text={text} />
</StyledMenuItemLabel>
</StyledMenuItemLeftContent>
);
}

View File

@@ -16,9 +16,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
cursor: pointer;
display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.sm};
@@ -29,8 +29,6 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
justify-content: space-between;
padding: var(--vertical-padding) var(--horizontal-padding);
${hoverBackground};
${({ theme, accent }) => {
@@ -43,6 +41,11 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
}
`;
}
case 'placeholder': {
return css`
color: ${theme.font.color.tertiary};
`;
}
case 'default':
default: {
return css`
@@ -52,7 +55,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
}
}}
padding: var(--vertical-padding) var(--horizontal-padding);
position: relative;
user-select: none;
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 { IconComponent } from '@/ui/icon/types/IconComponent';
type OwnProps = {
viewName: string;
viewIcon?: ReactNode;
ViewIcon?: IconComponent;
};
const StyledTitle = styled.div`
@@ -32,10 +34,13 @@ const StyledText = styled.span`
white-space: nowrap;
`;
export function ColumnHead({ viewName, viewIcon }: OwnProps) {
export function ColumnHead({ viewName, ViewIcon }: OwnProps) {
const theme = useTheme();
return (
<StyledTitle>
<StyledIcon>{viewIcon}</StyledIcon>
<StyledIcon>
{ViewIcon && <ViewIcon size={theme.icon.size.md} />}
</StyledIcon>
<StyledText>{viewName}</StyledText>
</StyledTitle>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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