mirror of
https://github.com/lingble/twenty.git
synced 2025-11-03 22:27:57 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -185,7 +185,7 @@ export function ActivityEditor({
|
||||
<>
|
||||
<DateEditableField
|
||||
value={activity.dueAt}
|
||||
icon={<IconCalendar />}
|
||||
Icon={IconCalendar}
|
||||
label="Due date"
|
||||
onSubmit={(newDate) => {
|
||||
updateActivityMutation({
|
||||
|
||||
@@ -23,7 +23,7 @@ export function ActivityAssigneeEditableField({ activity }: OwnProps) {
|
||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||
}}
|
||||
label="Assignee"
|
||||
iconLabel={<IconUserCircle />}
|
||||
IconLabel={IconUserCircle}
|
||||
editModeContent={
|
||||
<ActivityAssigneeEditableFieldEditMode activity={activity} />
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ActivityRelationEditableField({ activity }: OwnProps) {
|
||||
customEditHotkeyScope={{
|
||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||
}}
|
||||
iconLabel={<IconArrowUpRight />}
|
||||
IconLabel={IconArrowUpRight}
|
||||
editModeContent={
|
||||
<ActivityRelationEditableFieldEditMode activity={activity} />
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -109,7 +109,7 @@ export const TemplateColumn = <T extends string>({
|
||||
});
|
||||
const selectOptions = [
|
||||
{
|
||||
icon: <IconForbid />,
|
||||
icon: IconForbid,
|
||||
value: 'do-not-import',
|
||||
label: 'Do not import',
|
||||
},
|
||||
|
||||
@@ -134,7 +134,7 @@ export const generateColumns = <T extends string>(
|
||||
value={
|
||||
value
|
||||
? ({
|
||||
icon: null,
|
||||
icon: undefined,
|
||||
...value,
|
||||
} as const)
|
||||
: value
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -16,7 +16,7 @@ export function useBoardContextMenuEntries() {
|
||||
setContextMenuEntries([
|
||||
<ContextMenuEntry
|
||||
label="Delete"
|
||||
icon={<IconTrash size={16} />}
|
||||
Icon={IconTrash}
|
||||
accent="danger"
|
||||
onClick={() => deleteSelectedBoardCards()}
|
||||
key="delete"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
|
||||
export type BoardColumnDefinition = {
|
||||
id: string;
|
||||
title: string;
|
||||
index: number;
|
||||
colorCode: string;
|
||||
colorCode?: ThemeColor;
|
||||
};
|
||||
|
||||
@@ -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} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ export function GenericEditableBooleanField() {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||
<EditableField
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
displayModeContent={<GenericEditableBooleanFieldDisplayMode />}
|
||||
displayModeContentOnly
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function GenericEditableDateField() {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||
<EditableField
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
editModeContent={<GenericEditableDateFieldEditMode />}
|
||||
displayModeContent={<GenericEditableDateFieldDisplayMode />}
|
||||
isDisplayModeContentEmpty={!fieldValue}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function GenericEditableNumberField() {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||
<EditableField
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
editModeContent={<GenericEditableNumberFieldEditMode />}
|
||||
displayModeContent={fieldValue}
|
||||
isDisplayModeContentEmpty={!fieldValue}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function GenericEditableRelationField() {
|
||||
customEditHotkeyScope={{
|
||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||
}}
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
editModeContent={<GenericEditableRelationFieldEditMode />}
|
||||
displayModeContent={<GenericEditableRelationFieldDisplayMode />}
|
||||
isDisplayModeContentEmpty={!fieldValue}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function GenericEditableTextField() {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||
<EditableField
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
editModeContent={<GenericEditableTextFieldEditMode />}
|
||||
displayModeContent={fieldValue}
|
||||
isDisplayModeContentEmpty={!fieldValue}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -18,7 +18,7 @@ export function ProbabilityEditableField() {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||
<EditableField
|
||||
iconLabel={currentEditableFieldDefinition.icon}
|
||||
IconLabel={currentEditableFieldDefinition.Icon}
|
||||
displayModeContent={<ProbabilityEditableFieldEditMode />}
|
||||
displayModeContentOnly
|
||||
disableHoverEffect
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ?? ''}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
export type IconComponent = ComponentType<{ size: number }>;
|
||||
export type IconComponent = FunctionComponent<{ size?: number }>;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export type MenuItemProps = {
|
||||
LeftIcon?: IconComponent;
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
className: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function MenuItemNavigate({
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type MenuItemAccent = 'default' | 'danger';
|
||||
export type MenuItemAccent = 'default' | 'danger' | 'placeholder';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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})`;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,7 +8,7 @@ export const tasksFilters: FilterDefinitionByEntity<Activity>[] = [
|
||||
{
|
||||
key: 'assigneeId',
|
||||
label: 'Assignee',
|
||||
icon: <IconUser />,
|
||||
Icon: IconUser,
|
||||
type: 'entity',
|
||||
entitySelectComponent: (
|
||||
<FilterDropdownUserSearchSelect context={TasksRecoilScopeContext} />
|
||||
|
||||
Reference in New Issue
Block a user