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: Keep It Minimal
|
||||||
|
|
||||||
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through multiple component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
|
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through multiple component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
"lint": "eslint src --max-warnings=0",
|
"lint": "eslint src --max-warnings=0",
|
||||||
"storybook:dev": "storybook dev -p 6006 -s ../public",
|
"storybook:dev": "storybook dev -p 6006 -s ../public",
|
||||||
"storybook:test": "test-storybook",
|
"storybook:test": "test-storybook",
|
||||||
|
"storybook:test-slow": "test-storybook --maxWorkers=3",
|
||||||
"storybook:build": "storybook build -s public",
|
"storybook:build": "storybook build -s public",
|
||||||
"storybook:coverage": "test-storybook --coverage --maxWorkers=3 && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
|
"storybook:coverage": "test-storybook --coverage --maxWorkers=3 && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
|
||||||
"graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js",
|
"graphql:generate": "dotenv cross-var graphql-codegen --config codegen.js",
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export function ActivityEditor({
|
|||||||
<>
|
<>
|
||||||
<DateEditableField
|
<DateEditableField
|
||||||
value={activity.dueAt}
|
value={activity.dueAt}
|
||||||
icon={<IconCalendar />}
|
Icon={IconCalendar}
|
||||||
label="Due date"
|
label="Due date"
|
||||||
onSubmit={(newDate) => {
|
onSubmit={(newDate) => {
|
||||||
updateActivityMutation({
|
updateActivityMutation({
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function ActivityAssigneeEditableField({ activity }: OwnProps) {
|
|||||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||||
}}
|
}}
|
||||||
label="Assignee"
|
label="Assignee"
|
||||||
iconLabel={<IconUserCircle />}
|
IconLabel={IconUserCircle}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<ActivityAssigneeEditableFieldEditMode activity={activity} />
|
<ActivityAssigneeEditableFieldEditMode activity={activity} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function ActivityRelationEditableField({ activity }: OwnProps) {
|
|||||||
customEditHotkeyScope={{
|
customEditHotkeyScope={{
|
||||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||||
}}
|
}}
|
||||||
iconLabel={<IconArrowUpRight />}
|
IconLabel={IconArrowUpRight}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<ActivityRelationEditableFieldEditMode activity={activity} />
|
<ActivityRelationEditableFieldEditMode activity={activity} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export function CompanyBoardCard() {
|
|||||||
value={{
|
value={{
|
||||||
key: viewField.key,
|
key: viewField.key,
|
||||||
name: viewField.name,
|
name: viewField.name,
|
||||||
icon: viewField.icon,
|
Icon: viewField.Icon,
|
||||||
type: viewField.metadata.type,
|
type: viewField.metadata.type,
|
||||||
metadata: viewField.metadata,
|
metadata: viewField.metadata,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
@@ -13,6 +11,7 @@ import { IconChevronDown } from '@/ui/icon';
|
|||||||
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
|
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
|
||||||
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
|
import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
|
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
|
||||||
@@ -47,8 +46,6 @@ export function CompanyProgressPicker({
|
|||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [currentPipeline] = useRecoilState(currentPipelineState);
|
const [currentPipeline] = useRecoilState(currentPipelineState);
|
||||||
|
|
||||||
const currentPipelineStages = useMemo(
|
const currentPipelineStages = useMemo(
|
||||||
@@ -89,22 +86,21 @@ export function CompanyProgressPicker({
|
|||||||
{isProgressSelectionUnfolded ? (
|
{isProgressSelectionUnfolded ? (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{currentPipelineStages.map((pipelineStage, index) => (
|
{currentPipelineStages.map((pipelineStage, index) => (
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
key={pipelineStage.id}
|
key={pipelineStage.id}
|
||||||
data-testid={`select-pipeline-stage-${index}`}
|
testId={`select-pipeline-stage-${index}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlePipelineStageChange(pipelineStage.id);
|
handlePipelineStageChange(pipelineStage.id);
|
||||||
}}
|
}}
|
||||||
>
|
text={pipelineStage.name}
|
||||||
{pipelineStage.name}
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
data-testid="selected-pipeline-stage"
|
data-testid="selected-pipeline-stage"
|
||||||
endIcon={<IconChevronDown size={theme.icon.size.md} />}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsProgressSelectionUnfolded(true)}
|
onClick={() => setIsProgressSelectionUnfolded(true)}
|
||||||
>
|
>
|
||||||
{selectedPipelineStage?.name}
|
{selectedPipelineStage?.name}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
icon: <IconBuildingSkyscraper />,
|
Icon: IconBuildingSkyscraper,
|
||||||
size: 180,
|
size: 180,
|
||||||
index: 0,
|
index: 0,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -43,7 +43,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'domainName',
|
key: 'domainName',
|
||||||
name: 'URL',
|
name: 'URL',
|
||||||
icon: <IconLink />,
|
Icon: IconLink,
|
||||||
size: 100,
|
size: 100,
|
||||||
index: 1,
|
index: 1,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -56,7 +56,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'accountOwner',
|
key: 'accountOwner',
|
||||||
name: 'Account Owner',
|
name: 'Account Owner',
|
||||||
icon: <IconUserCircle />,
|
Icon: IconUserCircle,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 2,
|
index: 2,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -69,7 +69,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
name: 'Creation',
|
name: 'Creation',
|
||||||
icon: <IconCalendarEvent />,
|
Icon: IconCalendarEvent,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 3,
|
index: 3,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -81,7 +81,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'employees',
|
key: 'employees',
|
||||||
name: 'Employees',
|
name: 'Employees',
|
||||||
icon: <IconUsers />,
|
Icon: IconUsers,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 4,
|
index: 4,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -94,7 +94,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'linkedin',
|
key: 'linkedin',
|
||||||
name: 'LinkedIn',
|
name: 'LinkedIn',
|
||||||
icon: <IconBrandLinkedin />,
|
Icon: IconBrandLinkedin,
|
||||||
size: 170,
|
size: 170,
|
||||||
index: 5,
|
index: 5,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -107,7 +107,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'address',
|
key: 'address',
|
||||||
name: 'Address',
|
name: 'Address',
|
||||||
icon: <IconMap />,
|
Icon: IconMap,
|
||||||
size: 170,
|
size: 170,
|
||||||
index: 6,
|
index: 6,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -120,7 +120,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'idealCustomerProfile',
|
key: 'idealCustomerProfile',
|
||||||
name: 'ICP',
|
name: 'ICP',
|
||||||
icon: <IconTarget />,
|
Icon: IconTarget,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 7,
|
index: 7,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -132,7 +132,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'annualRecurringRevenue',
|
key: 'annualRecurringRevenue',
|
||||||
name: 'ARR',
|
name: 'ARR',
|
||||||
icon: <IconMoneybag />,
|
Icon: IconMoneybag,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 8,
|
index: 8,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -143,7 +143,7 @@ export const companiesAvailableColumnDefinitions: ColumnDefinition<ViewFieldMeta
|
|||||||
{
|
{
|
||||||
key: 'xUrl',
|
key: 'xUrl',
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
icon: <IconBrandX />,
|
Icon: IconBrandX,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 9,
|
index: 9,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ export function useCompanyTableContextMenuEntries() {
|
|||||||
setContextMenuEntries([
|
setContextMenuEntries([
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Note"
|
label="Note"
|
||||||
icon={<IconNotes size={16} />}
|
Icon={IconNotes}
|
||||||
onClick={() => handleButtonClick(ActivityType.Note)}
|
onClick={() => handleButtonClick(ActivityType.Note)}
|
||||||
key="note"
|
key="note"
|
||||||
/>,
|
/>,
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Task"
|
label="Task"
|
||||||
icon={<IconCheckbox size={16} />}
|
Icon={IconCheckbox}
|
||||||
onClick={() => handleButtonClick(ActivityType.Task)}
|
onClick={() => handleButtonClick(ActivityType.Task)}
|
||||||
key="task"
|
key="task"
|
||||||
/>,
|
/>,
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Delete"
|
label="Delete"
|
||||||
icon={<IconTrash size={16} />}
|
Icon={IconTrash}
|
||||||
accent="danger"
|
accent="danger"
|
||||||
onClick={() => deleteSelectedCompanies()}
|
onClick={() => deleteSelectedCompanies()}
|
||||||
key="delete"
|
key="delete"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardId
|
|||||||
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
|
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
|
||||||
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
||||||
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
|
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
|
||||||
|
import { isThemeColor } from '@/ui/theme/utils/castStringAsThemeColor';
|
||||||
import { Pipeline } from '~/generated/graphql';
|
import { Pipeline } from '~/generated/graphql';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
@@ -97,12 +98,22 @@ export function useUpdateCompanyBoard() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const newBoardColumns: BoardColumnDefinition[] =
|
const newBoardColumns: BoardColumnDefinition[] =
|
||||||
orderedPipelineStages?.map((pipelineStage) => ({
|
orderedPipelineStages?.map((pipelineStage) => {
|
||||||
id: pipelineStage.id,
|
if (!isThemeColor(pipelineStage.color)) {
|
||||||
title: pipelineStage.name,
|
console.warn(
|
||||||
colorCode: pipelineStage.color,
|
`Color ${pipelineStage.color} is not recognized in useUpdateCompanyBoard.`,
|
||||||
index: pipelineStage.index ?? 0,
|
);
|
||||||
}));
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: pipelineStage.id,
|
||||||
|
title: pipelineStage.name,
|
||||||
|
colorCode: isThemeColor(pipelineStage.color)
|
||||||
|
? pipelineStage.color
|
||||||
|
: undefined,
|
||||||
|
index: pipelineStage.index ?? 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) {
|
if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) {
|
||||||
set(boardColumnsState, newBoardColumns);
|
set(boardColumnsState, newBoardColumns);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
|
|
||||||
export const fieldsForCompany = [
|
export const fieldsForCompany = [
|
||||||
{
|
{
|
||||||
icon: <IconBuildingSkyscraper />,
|
icon: IconBuildingSkyscraper,
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
alternateMatches: ['name', 'company name', 'company'],
|
alternateMatches: ['name', 'company name', 'company'],
|
||||||
@@ -21,7 +21,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'Tim',
|
example: 'Tim',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconMail />,
|
icon: IconMail,
|
||||||
label: 'Domain name',
|
label: 'Domain name',
|
||||||
key: 'domainName',
|
key: 'domainName',
|
||||||
alternateMatches: ['domain', 'domain name'],
|
alternateMatches: ['domain', 'domain name'],
|
||||||
@@ -31,7 +31,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'apple.dev',
|
example: 'apple.dev',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBrandLinkedin />,
|
icon: IconBrandLinkedin,
|
||||||
label: 'Linkedin URL',
|
label: 'Linkedin URL',
|
||||||
key: 'linkedinUrl',
|
key: 'linkedinUrl',
|
||||||
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
|
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
|
||||||
@@ -41,7 +41,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'https://www.linkedin.com/in/apple',
|
example: 'https://www.linkedin.com/in/apple',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconMoneybag />,
|
icon: IconMoneybag,
|
||||||
label: 'ARR',
|
label: 'ARR',
|
||||||
key: 'annualRecurringRevenue',
|
key: 'annualRecurringRevenue',
|
||||||
alternateMatches: [
|
alternateMatches: [
|
||||||
@@ -64,7 +64,7 @@ export const fieldsForCompany = [
|
|||||||
example: '1000000',
|
example: '1000000',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconTarget />,
|
icon: IconTarget,
|
||||||
label: 'ICP',
|
label: 'ICP',
|
||||||
key: 'idealCustomerProfile',
|
key: 'idealCustomerProfile',
|
||||||
alternateMatches: [
|
alternateMatches: [
|
||||||
@@ -86,7 +86,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'true/false',
|
example: 'true/false',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBrandX />,
|
icon: IconBrandX,
|
||||||
label: 'x URL',
|
label: 'x URL',
|
||||||
key: 'xUrl',
|
key: 'xUrl',
|
||||||
alternateMatches: ['x', 'twitter', 'twitter url', 'x url'],
|
alternateMatches: ['x', 'twitter', 'twitter url', 'x url'],
|
||||||
@@ -96,7 +96,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'https://x.com/tim_cook',
|
example: 'https://x.com/tim_cook',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconMap />,
|
icon: IconMap,
|
||||||
label: 'Address',
|
label: 'Address',
|
||||||
key: 'address',
|
key: 'address',
|
||||||
fieldType: {
|
fieldType: {
|
||||||
@@ -105,7 +105,7 @@ export const fieldsForCompany = [
|
|||||||
example: 'Maple street',
|
example: 'Maple street',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconUsers />,
|
icon: IconUsers,
|
||||||
label: 'Employees',
|
label: 'Employees',
|
||||||
key: 'employees',
|
key: 'employees',
|
||||||
alternateMatches: ['employees', 'total employees', 'number of employees'],
|
alternateMatches: ['employees', 'total employees', 'number of employees'],
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
|
import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
|
||||||
|
|
||||||
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { IconDotsVertical, IconLinkOff, IconTrash } from '@/ui/icon';
|
import { IconDotsVertical, IconLinkOff, IconTrash } from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import {
|
import {
|
||||||
@@ -72,10 +71,6 @@ const StyledJobTitle = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledRemoveOption = styled.div`
|
|
||||||
color: ${({ theme }) => theme.color.red};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function PeopleCard({
|
export function PeopleCard({
|
||||||
person,
|
person,
|
||||||
hasBottomBorder = true,
|
hasBottomBorder = true,
|
||||||
@@ -93,8 +88,6 @@ export function PeopleCard({
|
|||||||
placement: 'right-start',
|
placement: 'right-start',
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [refs.floating],
|
refs: [refs.floating],
|
||||||
callback: () => {
|
callback: () => {
|
||||||
@@ -175,14 +168,17 @@ export function PeopleCard({
|
|||||||
<StyledDropdownMenuItemsContainer
|
<StyledDropdownMenuItemsContainer
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
|
<MenuItem
|
||||||
<IconLinkOff size={14} />
|
onClick={handleDetachPerson}
|
||||||
Detach relation
|
LeftIcon={IconLinkOff}
|
||||||
</DropdownMenuSelectableItem>
|
text="Detach relation"
|
||||||
<DropdownMenuSelectableItem onClick={handleDeletePerson}>
|
/>
|
||||||
<IconTrash size={14} color={theme.font.color.danger} />
|
<MenuItem
|
||||||
<StyledRemoveOption>Delete person</StyledRemoveOption>
|
onClick={handleDeletePerson}
|
||||||
</DropdownMenuSelectableItem>
|
LeftIcon={IconTrash}
|
||||||
|
text="Delete person"
|
||||||
|
accent="danger"
|
||||||
|
/>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'displayName',
|
key: 'displayName',
|
||||||
name: 'People',
|
name: 'People',
|
||||||
icon: <IconUser />,
|
Icon: IconUser,
|
||||||
size: 210,
|
size: 210,
|
||||||
index: 0,
|
index: 0,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -43,7 +43,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
icon: <IconMail />,
|
Icon: IconMail,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 1,
|
index: 1,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -55,7 +55,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'company',
|
key: 'company',
|
||||||
name: 'Company',
|
name: 'Company',
|
||||||
icon: <IconBuildingSkyscraper />,
|
Icon: IconBuildingSkyscraper,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 2,
|
index: 2,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -67,7 +67,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
name: 'Phone',
|
name: 'Phone',
|
||||||
icon: <IconPhone />,
|
Icon: IconPhone,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 3,
|
index: 3,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -79,7 +79,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
name: 'Creation',
|
name: 'Creation',
|
||||||
icon: <IconCalendarEvent />,
|
Icon: IconCalendarEvent,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 4,
|
index: 4,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -90,7 +90,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'city',
|
key: 'city',
|
||||||
name: 'City',
|
name: 'City',
|
||||||
icon: <IconMap />,
|
Icon: IconMap,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 5,
|
index: 5,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -102,7 +102,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'jobTitle',
|
key: 'jobTitle',
|
||||||
name: 'Job title',
|
name: 'Job title',
|
||||||
icon: <IconBriefcase />,
|
Icon: IconBriefcase,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 6,
|
index: 6,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -114,7 +114,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'linkedin',
|
key: 'linkedin',
|
||||||
name: 'LinkedIn',
|
name: 'LinkedIn',
|
||||||
icon: <IconBrandLinkedin />,
|
Icon: IconBrandLinkedin,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 7,
|
index: 7,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -126,7 +126,7 @@ export const peopleAvailableColumnDefinitions: ColumnDefinition<ViewFieldMetadat
|
|||||||
{
|
{
|
||||||
key: 'x',
|
key: 'x',
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
icon: <IconBrandX />,
|
Icon: IconBrandX,
|
||||||
size: 150,
|
size: 150,
|
||||||
index: 8,
|
index: 8,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|||||||
@@ -60,19 +60,19 @@ export function usePersonTableContextMenuEntries() {
|
|||||||
setContextMenuEntries([
|
setContextMenuEntries([
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Note"
|
label="Note"
|
||||||
icon={<IconNotes size={16} />}
|
Icon={IconNotes}
|
||||||
onClick={() => handleActivityClick(ActivityType.Note)}
|
onClick={() => handleActivityClick(ActivityType.Note)}
|
||||||
key="note"
|
key="note"
|
||||||
/>,
|
/>,
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Task"
|
label="Task"
|
||||||
icon={<IconCheckbox size={16} />}
|
Icon={IconCheckbox}
|
||||||
onClick={() => handleActivityClick(ActivityType.Task)}
|
onClick={() => handleActivityClick(ActivityType.Task)}
|
||||||
key="task"
|
key="task"
|
||||||
/>,
|
/>,
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Delete"
|
label="Delete"
|
||||||
icon={<IconTrash size={16} />}
|
Icon={IconTrash}
|
||||||
accent="danger"
|
accent="danger"
|
||||||
onClick={handleDeleteClick}
|
onClick={handleDeleteClick}
|
||||||
key="delete"
|
key="delete"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { isValidPhoneNumber } from 'libphonenumber-js';
|
import { isValidPhoneNumber } from 'libphonenumber-js';
|
||||||
|
|
||||||
|
import { Fields } from '@/spreadsheet-import/types';
|
||||||
import {
|
import {
|
||||||
IconBrandLinkedin,
|
IconBrandLinkedin,
|
||||||
IconBrandX,
|
IconBrandX,
|
||||||
@@ -11,7 +12,7 @@ import {
|
|||||||
|
|
||||||
export const fieldsForPerson = [
|
export const fieldsForPerson = [
|
||||||
{
|
{
|
||||||
icon: <IconUser />,
|
icon: IconUser,
|
||||||
label: 'Firstname',
|
label: 'Firstname',
|
||||||
key: 'firstName',
|
key: 'firstName',
|
||||||
alternateMatches: ['first name', 'first', 'firstname'],
|
alternateMatches: ['first name', 'first', 'firstname'],
|
||||||
@@ -21,7 +22,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'Tim',
|
example: 'Tim',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconUser />,
|
icon: IconUser,
|
||||||
label: 'Lastname',
|
label: 'Lastname',
|
||||||
key: 'lastName',
|
key: 'lastName',
|
||||||
alternateMatches: ['last name', 'last', 'lastname'],
|
alternateMatches: ['last name', 'last', 'lastname'],
|
||||||
@@ -31,7 +32,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'Cook',
|
example: 'Cook',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconMail />,
|
icon: IconMail,
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
key: 'email',
|
key: 'email',
|
||||||
alternateMatches: ['email', 'mail'],
|
alternateMatches: ['email', 'mail'],
|
||||||
@@ -41,7 +42,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'tim@apple.dev',
|
example: 'tim@apple.dev',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBrandLinkedin />,
|
icon: IconBrandLinkedin,
|
||||||
label: 'Linkedin URL',
|
label: 'Linkedin URL',
|
||||||
key: 'linkedinUrl',
|
key: 'linkedinUrl',
|
||||||
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
|
alternateMatches: ['linkedIn', 'linkedin', 'linkedin url'],
|
||||||
@@ -51,7 +52,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'https://www.linkedin.com/in/timcook',
|
example: 'https://www.linkedin.com/in/timcook',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBrandX />,
|
icon: IconBrandX,
|
||||||
label: 'X URL',
|
label: 'X URL',
|
||||||
key: 'xUrl',
|
key: 'xUrl',
|
||||||
alternateMatches: ['x', 'x url'],
|
alternateMatches: ['x', 'x url'],
|
||||||
@@ -61,7 +62,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'https://x.com/tim_cook',
|
example: 'https://x.com/tim_cook',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBriefcase />,
|
icon: IconBriefcase,
|
||||||
label: 'Job title',
|
label: 'Job title',
|
||||||
key: 'jobTitle',
|
key: 'jobTitle',
|
||||||
alternateMatches: ['job', 'job title'],
|
alternateMatches: ['job', 'job title'],
|
||||||
@@ -71,7 +72,7 @@ export const fieldsForPerson = [
|
|||||||
example: 'CEO',
|
example: 'CEO',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconBriefcase />,
|
icon: IconBriefcase,
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
fieldType: {
|
fieldType: {
|
||||||
@@ -88,7 +89,7 @@ export const fieldsForPerson = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconMap />,
|
icon: IconMap,
|
||||||
label: 'City',
|
label: 'City',
|
||||||
key: 'city',
|
key: 'city',
|
||||||
fieldType: {
|
fieldType: {
|
||||||
@@ -96,4 +97,4 @@ export const fieldsForPerson = [
|
|||||||
},
|
},
|
||||||
example: 'Seattle',
|
example: 'Seattle',
|
||||||
},
|
},
|
||||||
] as const;
|
] as Fields<string>;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
|
|||||||
{
|
{
|
||||||
key: 'closeDate',
|
key: 'closeDate',
|
||||||
name: 'Close Date',
|
name: 'Close Date',
|
||||||
icon: <IconCalendarEvent />,
|
Icon: IconCalendarEvent,
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
fieldName: 'closeDate',
|
fieldName: 'closeDate',
|
||||||
@@ -29,7 +29,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
|
|||||||
{
|
{
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
name: 'Amount',
|
name: 'Amount',
|
||||||
icon: <IconCurrencyDollar />,
|
Icon: IconCurrencyDollar,
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
fieldName: 'amount',
|
fieldName: 'amount',
|
||||||
@@ -39,7 +39,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
|
|||||||
{
|
{
|
||||||
key: 'probability',
|
key: 'probability',
|
||||||
name: 'Probability',
|
name: 'Probability',
|
||||||
icon: <IconProgressCheck />,
|
Icon: IconProgressCheck,
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'probability',
|
type: 'probability',
|
||||||
fieldName: 'probability',
|
fieldName: 'probability',
|
||||||
@@ -49,7 +49,7 @@ export const pipelineAvailableFieldDefinitions: ViewFieldDefinition<ViewFieldMet
|
|||||||
{
|
{
|
||||||
key: 'pointOfContact',
|
key: 'pointOfContact',
|
||||||
name: 'Point of Contact',
|
name: 'Point of Contact',
|
||||||
icon: <IconUser />,
|
Icon: IconUser,
|
||||||
metadata: {
|
metadata: {
|
||||||
type: 'relation',
|
type: 'relation',
|
||||||
fieldName: 'pointOfContact',
|
fieldName: 'pointOfContact',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const usePipelineStages = () => {
|
|||||||
return createPipelineStageMutation({
|
return createPipelineStageMutation({
|
||||||
variables: {
|
variables: {
|
||||||
data: {
|
data: {
|
||||||
color: boardColumn.colorCode,
|
color: boardColumn.colorCode ?? 'gray',
|
||||||
id: boardColumn.id,
|
id: boardColumn.id,
|
||||||
index: boardColumn.index,
|
index: boardColumn.index,
|
||||||
name: boardColumn.title,
|
name: boardColumn.title,
|
||||||
|
|||||||
@@ -14,44 +14,15 @@ import { ReadonlyDeep } from 'type-fest';
|
|||||||
|
|
||||||
import type { SelectOption } from '@/spreadsheet-import/types';
|
import type { SelectOption } from '@/spreadsheet-import/types';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconChevronDown, TablerIconsProps } from '@/ui/icon';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect';
|
||||||
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
|
import { AppTooltip } from '@/ui/tooltip/AppTooltip';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||||
|
|
||||||
const StyledDropdownItem = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
background-color: ${({ theme }) => theme.background.tertiary};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
height: 32px;
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: ${({ theme }) => theme.background.quaternary};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledDropdownLabel = styled.span<{ isPlaceholder: boolean }>`
|
|
||||||
color: ${({ theme, isPlaceholder }) =>
|
|
||||||
isPlaceholder ? theme.font.color.tertiary : theme.font.color.primary};
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFloatingDropdown = styled.div`
|
const StyledFloatingDropdown = styled.div`
|
||||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||||
`;
|
`;
|
||||||
@@ -69,11 +40,9 @@ export const MatchColumnSelect = ({
|
|||||||
value,
|
value,
|
||||||
options: initialOptions,
|
options: initialOptions,
|
||||||
placeholder,
|
placeholder,
|
||||||
name,
|
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const dropdownItemRef = useRef<HTMLDivElement>(null);
|
|
||||||
const dropdownContainerRef = useRef<HTMLDivElement>(null);
|
const dropdownContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -123,16 +92,6 @@ export const MatchColumnSelect = ({
|
|||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderIcon(icon: ReadonlyDeep<React.ReactNode>) {
|
|
||||||
if (icon && React.isValidElement(icon)) {
|
|
||||||
return React.cloneElement<TablerIconsProps>(icon as any, {
|
|
||||||
size: 16,
|
|
||||||
color: theme.font.color.primary,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [dropdownContainerRef],
|
refs: [dropdownContainerRef],
|
||||||
callback: () => {
|
callback: () => {
|
||||||
@@ -146,28 +105,20 @@ export const MatchColumnSelect = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledDropdownItem
|
<div ref={refs.setReference}>
|
||||||
id={name}
|
<MenuItem
|
||||||
ref={(node) => {
|
LeftIcon={value?.icon}
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
onClick={handleDropdownItemClick}
|
||||||
// @ts-expect-error
|
text={value?.label ?? placeholder ?? ''}
|
||||||
dropdownItemRef.current = node;
|
accent={value?.label ? 'default' : 'placeholder'}
|
||||||
refs.setReference(node);
|
/>
|
||||||
}}
|
</div>
|
||||||
onClick={handleDropdownItemClick}
|
|
||||||
>
|
|
||||||
{renderIcon(value?.icon)}
|
|
||||||
<StyledDropdownLabel isPlaceholder={!value?.label}>
|
|
||||||
{value?.label ?? placeholder}
|
|
||||||
</StyledDropdownLabel>
|
|
||||||
<IconChevronDown size={16} color={theme.font.color.tertiary} />
|
|
||||||
</StyledDropdownItem>
|
|
||||||
{isOpen &&
|
{isOpen &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
|
<StyledFloatingDropdown ref={refs.setFloating} style={floatingStyles}>
|
||||||
<StyledDropdownMenu
|
<StyledDropdownMenu
|
||||||
ref={dropdownContainerRef}
|
ref={dropdownContainerRef}
|
||||||
width={dropdownItemRef.current?.clientWidth}
|
width={refs.domReference.current?.clientWidth}
|
||||||
>
|
>
|
||||||
<DropdownMenuInput
|
<DropdownMenuInput
|
||||||
value={searchFilter}
|
value={searchFilter}
|
||||||
@@ -178,18 +129,16 @@ export const MatchColumnSelect = ({
|
|||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{options?.map((option) => (
|
{options?.map((option) => (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSelectableItem
|
<MenuItemSelect
|
||||||
id={option.value}
|
|
||||||
key={option.label}
|
key={option.label}
|
||||||
selected={value?.label === option.label}
|
selected={value?.label === option.label}
|
||||||
onClick={() => handleChange(option)}
|
onClick={() => handleChange(option)}
|
||||||
disabled={
|
disabled={
|
||||||
option.disabled && value?.value !== option.value
|
option.disabled && value?.value !== option.value
|
||||||
}
|
}
|
||||||
>
|
LeftIcon={option?.icon}
|
||||||
{renderIcon(option?.icon)}
|
text={option.label}
|
||||||
{option.label}
|
/>
|
||||||
</DropdownMenuSelectableItem>
|
|
||||||
{option.disabled &&
|
{option.disabled &&
|
||||||
value?.value !== option.value &&
|
value?.value !== option.value &&
|
||||||
createPortal(
|
createPortal(
|
||||||
@@ -204,9 +153,7 @@ export const MatchColumnSelect = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
{options?.length === 0 && (
|
{options?.length === 0 && <MenuItem text="No result" />}
|
||||||
<DropdownMenuItem>No result</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
</StyledFloatingDropdown>,
|
</StyledFloatingDropdown>,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const TemplateColumn = <T extends string>({
|
|||||||
});
|
});
|
||||||
const selectOptions = [
|
const selectOptions = [
|
||||||
{
|
{
|
||||||
icon: <IconForbid />,
|
icon: IconForbid,
|
||||||
value: 'do-not-import',
|
value: 'do-not-import',
|
||||||
label: 'Do not import',
|
label: 'Do not import',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const generateColumns = <T extends string>(
|
|||||||
value={
|
value={
|
||||||
value
|
value
|
||||||
? ({
|
? ({
|
||||||
icon: null,
|
icon: undefined,
|
||||||
...value,
|
...value,
|
||||||
} as const)
|
} as const)
|
||||||
: value
|
: value
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport';
|
import { defaultSpreadsheetImportProps } from '@/spreadsheet-import/provider/components/SpreadsheetImport';
|
||||||
import type { SpreadsheetOptions } from '@/spreadsheet-import/types';
|
import type { Fields, SpreadsheetOptions } from '@/spreadsheet-import/types';
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
@@ -85,7 +85,7 @@ const fields = [
|
|||||||
},
|
},
|
||||||
example: 'true',
|
example: 'true',
|
||||||
},
|
},
|
||||||
] as const;
|
] as Fields<string>;
|
||||||
|
|
||||||
const mockComponentBehaviourForTypes = <T extends string>(
|
const mockComponentBehaviourForTypes = <T extends string>(
|
||||||
props: SpreadsheetOptions<T>,
|
props: SpreadsheetOptions<T>,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ReadonlyDeep } from 'type-fest';
|
|||||||
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
import { Columns } from '@/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep';
|
||||||
import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow';
|
import { StepState } from '@/spreadsheet-import/steps/components/UploadFlow';
|
||||||
import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types';
|
import { Meta } from '@/spreadsheet-import/steps/components/ValidationStep/types';
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
export type SpreadsheetOptions<Keys extends string> = {
|
export type SpreadsheetOptions<Keys extends string> = {
|
||||||
// Is modal visible.
|
// Is modal visible.
|
||||||
@@ -65,7 +66,7 @@ export type Fields<T extends string> = ReadonlyDeep<Field<T>[]>;
|
|||||||
|
|
||||||
export type Field<T extends string> = {
|
export type Field<T extends string> = {
|
||||||
// Icon
|
// Icon
|
||||||
icon: React.ReactNode;
|
icon: IconComponent | null | undefined;
|
||||||
// UI-facing field label
|
// UI-facing field label
|
||||||
label: string;
|
label: string;
|
||||||
// Field's unique identifier
|
// Field's unique identifier
|
||||||
@@ -96,7 +97,7 @@ export type Select = {
|
|||||||
|
|
||||||
export type SelectOption = {
|
export type SelectOption = {
|
||||||
// Icon
|
// Icon
|
||||||
icon?: React.ReactNode;
|
icon?: IconComponent | null;
|
||||||
// UI-facing option label
|
// UI-facing option label
|
||||||
label: string;
|
label: string;
|
||||||
// Field entry matching criteria as well as select output
|
// Field entry matching criteria as well as select output
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { Tag } from '@/ui/tag/components/Tag';
|
import { Tag } from '@/ui/tag/components/Tag';
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
|
||||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
@@ -52,7 +53,7 @@ const StyledNumChildren = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type BoardColumnProps = {
|
export type BoardColumnProps = {
|
||||||
color: string;
|
color?: ThemeColor;
|
||||||
title: string;
|
title: string;
|
||||||
onDelete?: (id: string) => void;
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
@@ -97,7 +98,7 @@ export function BoardColumn({
|
|||||||
return (
|
return (
|
||||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||||
<StyledHeader>
|
<StyledHeader>
|
||||||
<Tag onClick={handleTitleClick} color={color} text={title} />
|
<Tag onClick={handleTitleClick} color={color ?? 'gray'} text={title} />
|
||||||
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
||||||
<StyledNumChildren>{numChildren}</StyledNumChildren>
|
<StyledNumChildren>{numChildren}</StyledNumChildren>
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
@@ -107,7 +108,7 @@ export function BoardColumn({
|
|||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onTitleEdit={onTitleEdit}
|
onTitleEdit={onTitleEdit}
|
||||||
title={title}
|
title={title}
|
||||||
color={color}
|
color={color ?? 'gray'}
|
||||||
stageId={stageId}
|
stageId={stageId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { ChangeEvent, useState } from 'react';
|
import { ChangeEvent, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
|
import { MenuItemSelectColor } from '@/ui/menu-item/components/MenuItemSelectColor';
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
import { textInputStyle } from '@/ui/theme/constants/effects';
|
import { textInputStyle } from '@/ui/theme/constants/effects';
|
||||||
import { debounce } from '~/utils/debounce';
|
import { debounce } from '~/utils/debounce';
|
||||||
|
|
||||||
@@ -32,21 +33,15 @@ export type BoardColumnEditTitleMenuProps = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
color: string;
|
color: ThemeColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledColorSample = styled.div<{ colorName: string }>`
|
type ColumnColorOption = {
|
||||||
background-color: ${({ theme, colorName }) =>
|
name: string;
|
||||||
theme.tag.background[colorName]};
|
id: ThemeColor;
|
||||||
border: 1px solid
|
};
|
||||||
${({ theme, colorName }) =>
|
|
||||||
theme.color[colorName as keyof typeof theme.color]};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const COLOR_OPTIONS = [
|
export const COLUMN_COLOR_OPTIONS: ColumnColorOption[] = [
|
||||||
{ name: 'Green', id: 'green' },
|
{ name: 'Green', id: 'green' },
|
||||||
{ name: 'Turquoise', id: 'turquoise' },
|
{ name: 'Turquoise', id: 'turquoise' },
|
||||||
{ name: 'Sky', id: 'sky' },
|
{ name: 'Sky', id: 'sky' },
|
||||||
@@ -85,18 +80,17 @@ export function BoardColumnEditTitleMenu({
|
|||||||
/>
|
/>
|
||||||
</StyledEditTitleContainer>
|
</StyledEditTitleContainer>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
{COLOR_OPTIONS.map((colorOption) => (
|
{COLUMN_COLOR_OPTIONS.map((colorOption) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItemSelectColor
|
||||||
key={colorOption.name}
|
key={colorOption.name}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onTitleEdit(title, colorOption.id);
|
onTitleEdit(title, colorOption.id);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
|
color={colorOption.id}
|
||||||
selected={colorOption.id === color}
|
selected={colorOption.id === color}
|
||||||
>
|
text={colorOption.name}
|
||||||
<StyledColorSample colorName={colorOption.id} />
|
/>
|
||||||
{colorOption.name}
|
|
||||||
</DropdownMenuSelectableItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Key } from 'ts-key-enum';
|
|||||||
|
|
||||||
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
|
import { useCreateCompanyProgress } from '@/companies/hooks/useCreateCompanyProgress';
|
||||||
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
|
import { useFilteredSearchCompanyQuery } from '@/companies/hooks/useFilteredSearchCompanyQuery';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { IconPencil, IconPlus, IconTrash } from '@/ui/icon';
|
import { IconPencil, IconPlus, IconTrash } from '@/ui/icon';
|
||||||
@@ -13,8 +12,9 @@ import { SingleEntitySelect } from '@/ui/input/relation-picker/components/Single
|
|||||||
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||||
import { icon } from '@/ui/theme/constants/icon';
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
@@ -32,7 +32,7 @@ const StyledMenuContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
color: string;
|
color: ThemeColor;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onDelete?: (id: string) => void;
|
onDelete?: (id: string) => void;
|
||||||
onTitleEdit: (title: string, color: string) => void;
|
onTitleEdit: (title: string, color: string) => void;
|
||||||
@@ -52,7 +52,7 @@ export function BoardColumnMenu({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [currentMenu, setCurrentMenu] = useState('actions');
|
const [currentMenu, setCurrentMenu] = useState('actions');
|
||||||
|
|
||||||
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
const [, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||||
|
|
||||||
const boardColumnMenuRef = useRef(null);
|
const boardColumnMenuRef = useRef(null);
|
||||||
|
|
||||||
@@ -130,21 +130,21 @@ export function BoardColumnMenu({
|
|||||||
<StyledDropdownMenu>
|
<StyledDropdownMenu>
|
||||||
{currentMenu === 'actions' && (
|
{currentMenu === 'actions' && (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuSelectableItem onClick={() => setMenu('title')}>
|
<MenuItem
|
||||||
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
onClick={() => setMenu('title')}
|
||||||
Rename
|
LeftIcon={IconPencil}
|
||||||
</DropdownMenuSelectableItem>
|
text="Rename"
|
||||||
<DropdownMenuSelectableItem
|
/>
|
||||||
disabled={boardColumns.length <= 1}
|
<MenuItem
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
LeftIcon={IconTrash}
|
||||||
<IconTrash size={icon.size.md} stroke={icon.stroke.sm} />
|
text="Delete"
|
||||||
Delete
|
/>
|
||||||
</DropdownMenuSelectableItem>
|
<MenuItem
|
||||||
<DropdownMenuSelectableItem onClick={() => setMenu('add')}>
|
onClick={() => setMenu('add')}
|
||||||
<IconPlus size={icon.size.md} stroke={icon.stroke.sm} />
|
LeftIcon={IconPlus}
|
||||||
New opportunity
|
text="New opportunity"
|
||||||
</DropdownMenuSelectableItem>
|
/>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
)}
|
)}
|
||||||
{currentMenu === 'title' && (
|
{currentMenu === 'title' && (
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ import { v4 } from 'uuid';
|
|||||||
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import {
|
import {
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
|
||||||
IconLayoutKanban,
|
IconLayoutKanban,
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
} from '@/ui/icon';
|
} from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate';
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
@@ -35,16 +36,18 @@ const StyledIconSettings = styled(IconSettings)`
|
|||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
margin-left: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
enum BoardOptionsMenu {
|
enum BoardOptionsMenu {
|
||||||
StageCreation = 'StageCreation',
|
StageCreation = 'StageCreation',
|
||||||
Stages = 'Stages',
|
Stages = 'Stages',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ColumnForCreate = {
|
||||||
|
id: string;
|
||||||
|
colorCode: ThemeColor;
|
||||||
|
index: number;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function BoardOptionsDropdownContent({
|
export function BoardOptionsDropdownContent({
|
||||||
customHotkeyScope,
|
customHotkeyScope,
|
||||||
onStageAdd,
|
onStageAdd,
|
||||||
@@ -68,7 +71,7 @@ export function BoardOptionsDropdownContent({
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const columnToCreate = {
|
const columnToCreate: ColumnForCreate = {
|
||||||
id: v4(),
|
id: v4(),
|
||||||
colorCode: 'gray',
|
colorCode: 'gray',
|
||||||
index: boardColumns.length,
|
index: boardColumns.length,
|
||||||
@@ -113,32 +116,26 @@ export function BoardOptionsDropdownContent({
|
|||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem
|
<MenuItemNavigate
|
||||||
onClick={() => setCurrentMenu(BoardOptionsMenu.Stages)}
|
onClick={() => setCurrentMenu(BoardOptionsMenu.Stages)}
|
||||||
>
|
LeftIcon={IconLayoutKanban}
|
||||||
<IconLayoutKanban size={theme.icon.size.md} />
|
text="Stages"
|
||||||
Stages
|
/>
|
||||||
<StyledIconChevronRight size={theme.icon.size.sm} />
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{currentMenu === BoardOptionsMenu.Stages && (
|
{currentMenu === BoardOptionsMenu.Stages && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||||
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
|
||||||
onClick={resetMenu}
|
|
||||||
>
|
|
||||||
Stages
|
Stages
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
onClick={() => setCurrentMenu(BoardOptionsMenu.StageCreation)}
|
onClick={() => setCurrentMenu(BoardOptionsMenu.StageCreation)}
|
||||||
>
|
LeftIcon={IconPlus}
|
||||||
<IconPlus size={theme.icon.size.md} />
|
text="Add stage"
|
||||||
Add stage
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BoardColumnEditTitleMenu,
|
BoardColumnEditTitleMenu,
|
||||||
COLOR_OPTIONS,
|
COLUMN_COLOR_OPTIONS,
|
||||||
} from '../BoardColumnEditTitleMenu';
|
} from '../BoardColumnEditTitleMenu';
|
||||||
|
|
||||||
const meta: Meta<typeof BoardColumnEditTitleMenu> = {
|
const meta: Meta<typeof BoardColumnEditTitleMenu> = {
|
||||||
@@ -14,7 +14,7 @@ const meta: Meta<typeof BoardColumnEditTitleMenu> = {
|
|||||||
argTypes: {
|
argTypes: {
|
||||||
color: {
|
color: {
|
||||||
control: 'select',
|
control: 'select',
|
||||||
options: COLOR_OPTIONS.map(({ id }) => id),
|
options: COLUMN_COLOR_OPTIONS.map(({ id }) => id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
args: { color: 'green', title: 'Column title' },
|
args: { color: 'green', title: 'Column title' },
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function useBoardContextMenuEntries() {
|
|||||||
setContextMenuEntries([
|
setContextMenuEntries([
|
||||||
<ContextMenuEntry
|
<ContextMenuEntry
|
||||||
label="Delete"
|
label="Delete"
|
||||||
icon={<IconTrash size={16} />}
|
Icon={IconTrash}
|
||||||
accent="danger"
|
accent="danger"
|
||||||
onClick={() => deleteSelectedBoardCards()}
|
onClick={() => deleteSelectedBoardCards()}
|
||||||
key="delete"
|
key="delete"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
export type BoardColumnDefinition = {
|
export type BoardColumnDefinition = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
index: number;
|
index: number;
|
||||||
colorCode: string;
|
colorCode?: ThemeColor;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,32 +1,22 @@
|
|||||||
import { ReactNode } from 'react';
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
import styled from '@emotion/styled';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
type ContextMenuEntryAccent = 'default' | 'danger';
|
||||||
|
|
||||||
type ContextMenuEntryAccent = 'regular' | 'danger';
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
icon: ReactNode;
|
Icon: IconComponent;
|
||||||
label: string;
|
label: string;
|
||||||
accent?: ContextMenuEntryAccent;
|
accent?: ContextMenuEntryAccent;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButtonLabel = styled.div`
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
||||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function ContextMenuEntry({
|
export function ContextMenuEntry({
|
||||||
label,
|
label,
|
||||||
icon,
|
Icon,
|
||||||
accent = 'regular',
|
accent = 'default',
|
||||||
onClick,
|
onClick,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem onClick={onClick} accent={accent}>
|
<MenuItem LeftIcon={Icon} onClick={onClick} accent={accent} text={label} />
|
||||||
{icon}
|
|
||||||
<StyledButtonLabel>{label}</StyledButtonLabel>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
const StyledHeader = styled.li`
|
const StyledHeader = styled.li`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
@@ -40,23 +43,31 @@ const StyledEndIconWrapper = styled(StyledStartIconWrapper)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type DropdownMenuHeaderProps = ComponentProps<'li'> & {
|
type DropdownMenuHeaderProps = ComponentProps<'li'> & {
|
||||||
startIcon?: ReactElement;
|
StartIcon?: IconComponent;
|
||||||
endIcon?: ReactElement;
|
EndIcon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DropdownMenuHeader({
|
export function DropdownMenuHeader({
|
||||||
children,
|
children,
|
||||||
startIcon,
|
StartIcon,
|
||||||
endIcon,
|
EndIcon,
|
||||||
...props
|
...props
|
||||||
}: DropdownMenuHeaderProps) {
|
}: DropdownMenuHeaderProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledHeader {...props}>
|
<StyledHeader {...props}>
|
||||||
{startIcon && (
|
{StartIcon && (
|
||||||
<StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper>
|
<StyledStartIconWrapper>
|
||||||
|
<StartIcon size={theme.icon.size.md} />
|
||||||
|
</StyledStartIconWrapper>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>}
|
{EndIcon && (
|
||||||
|
<StyledEndIconWrapper>
|
||||||
|
<EndIcon size={theme.icon.size.md} />
|
||||||
|
</StyledEndIconWrapper>
|
||||||
|
)}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
overflow-y: auto;
|
||||||
|
|
||||||
padding: var(--padding);
|
padding: var(--padding);
|
||||||
|
padding-right: var(--padding);
|
||||||
width: calc(100% - 2 * var(--padding));
|
width: calc(100% - 2 * var(--padding));
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ import { useState } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
|
||||||
import { IconPlus, IconUser } from '@/ui/icon';
|
import { IconPlus, IconUser } from '@/ui/icon';
|
||||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar';
|
||||||
|
import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
|
||||||
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '../DropdownMenuInput';
|
import { DropdownMenuInput } from '../DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '../DropdownMenuItem';
|
|
||||||
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenu } from '../StyledDropdownMenu';
|
import { StyledDropdownMenu } from '../StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '../StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '../StyledDropdownMenuSeparator';
|
||||||
@@ -101,21 +100,22 @@ const FakeSelectableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{mockSelectArray.map((item) => (
|
{mockSelectArray.map((item) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItemSelectAvatar
|
||||||
key={item.id}
|
key={item.id}
|
||||||
selected={selectedItem === item.id}
|
selected={selectedItem === item.id}
|
||||||
onClick={() => setSelectedItem(item.id)}
|
onClick={() => setSelectedItem(item.id)}
|
||||||
>
|
avatar={
|
||||||
{hasAvatar && (
|
hasAvatar ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
placeholder="A"
|
placeholder="A"
|
||||||
avatarUrl={item.avatarUrl}
|
avatarUrl={item.avatarUrl}
|
||||||
size="md"
|
size="md"
|
||||||
type="squared"
|
type="squared"
|
||||||
/>
|
/>
|
||||||
)}
|
) : undefined
|
||||||
{item.name}
|
}
|
||||||
</DropdownMenuSelectableItem>
|
text={item.name}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -127,28 +127,28 @@ const FakeCheckableMenuItemList = ({ hasAvatar }: { hasAvatar?: boolean }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{mockSelectArray.map((item) => (
|
{mockSelectArray.map((item) => (
|
||||||
<DropdownMenuCheckableItem
|
<MenuItemMultiSelectAvatar
|
||||||
key={item.id}
|
key={item.id}
|
||||||
id={item.id}
|
selected={selectedItems.includes(item.id)}
|
||||||
checked={selectedItems.includes(item.id)}
|
onSelectChange={(checked) => {
|
||||||
onChange={(checked) => {
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSelectedItems([...selectedItems, item.id]);
|
setSelectedItems([...selectedItems, item.id]);
|
||||||
} else {
|
} else {
|
||||||
setSelectedItems(selectedItems.filter((id) => id !== item.id));
|
setSelectedItems(selectedItems.filter((id) => id !== item.id));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
avatar={
|
||||||
{hasAvatar && (
|
hasAvatar ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
placeholder="A"
|
placeholder="A"
|
||||||
avatarUrl={item.avatarUrl}
|
avatarUrl={item.avatarUrl}
|
||||||
size="md"
|
size="md"
|
||||||
type="squared"
|
type="squared"
|
||||||
/>
|
/>
|
||||||
)}
|
) : undefined
|
||||||
{item.name}
|
}
|
||||||
</DropdownMenuCheckableItem>
|
text={item.name}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -182,7 +182,7 @@ export const SimpleMenuItem: Story = {
|
|||||||
<StyledDropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<MenuItem text={name} />
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
@@ -198,14 +198,14 @@ export const WithHeaders: Story = {
|
|||||||
<StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>Subheader 1</StyledDropdownMenuSubheader>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{mockSelectArray.slice(0, 3).map(({ name }) => (
|
{mockSelectArray.slice(0, 3).map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<MenuItem text={name} />
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>Subheader 2</StyledDropdownMenuSubheader>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{mockSelectArray.slice(3).map(({ name }) => (
|
{mockSelectArray.slice(3).map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<MenuItem text={name} />
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
@@ -218,10 +218,7 @@ export const WithIcons: Story = {
|
|||||||
<StyledDropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>
|
<MenuItem text={name} LeftIcon={IconUser} />
|
||||||
<IconUser size={16} />
|
|
||||||
{name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
@@ -234,15 +231,11 @@ export const WithActions: Story = {
|
|||||||
<StyledDropdownMenu {...args}>
|
<StyledDropdownMenu {...args}>
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }, index) => (
|
{mockSelectArray.map(({ name }, index) => (
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
className={index === 0 ? 'hover' : undefined}
|
className={index === 0 ? 'hover' : undefined}
|
||||||
actions={[
|
iconButtons={[{ Icon: IconUser }, { Icon: IconPlus }]}
|
||||||
<IconButton icon={<IconUser />} />,
|
text={name}
|
||||||
<IconButton icon={<IconPlus />} />,
|
/>
|
||||||
]}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
@@ -273,7 +266,7 @@ export const Search: Story = {
|
|||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{mockSelectArray.map(({ name }) => (
|
{mockSelectArray.map(({ name }) => (
|
||||||
<DropdownMenuItem>{name}</DropdownMenuItem>
|
<MenuItem text={name} />
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
import { useEditableField } from '../hooks/useEditableField';
|
import { useEditableField } from '../hooks/useEditableField';
|
||||||
@@ -71,7 +72,7 @@ const StyledEditableFieldBaseContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
iconLabel?: React.ReactNode;
|
IconLabel?: IconComponent;
|
||||||
label?: string;
|
label?: string;
|
||||||
labelFixedWidth?: number;
|
labelFixedWidth?: number;
|
||||||
useEditButton?: boolean;
|
useEditButton?: boolean;
|
||||||
@@ -87,7 +88,7 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function EditableField({
|
export function EditableField({
|
||||||
iconLabel,
|
IconLabel,
|
||||||
label,
|
label,
|
||||||
labelFixedWidth,
|
labelFixedWidth,
|
||||||
useEditButton,
|
useEditButton,
|
||||||
@@ -125,7 +126,11 @@ export function EditableField({
|
|||||||
onMouseLeave={handleContainerMouseLeave}
|
onMouseLeave={handleContainerMouseLeave}
|
||||||
>
|
>
|
||||||
<StyledLabelAndIconContainer>
|
<StyledLabelAndIconContainer>
|
||||||
{iconLabel && <StyledIconContainer>{iconLabel}</StyledIconContainer>}
|
{IconLabel && (
|
||||||
|
<StyledIconContainer>
|
||||||
|
<IconLabel />
|
||||||
|
</StyledIconContainer>
|
||||||
|
)}
|
||||||
{label && (
|
{label && (
|
||||||
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
|
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function GenericEditableBooleanField() {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
displayModeContent={<GenericEditableBooleanFieldDisplayMode />}
|
displayModeContent={<GenericEditableBooleanFieldDisplayMode />}
|
||||||
displayModeContentOnly
|
displayModeContentOnly
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function GenericEditableDateField() {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditableDateFieldEditMode />}
|
editModeContent={<GenericEditableDateFieldEditMode />}
|
||||||
displayModeContent={<GenericEditableDateFieldDisplayMode />}
|
displayModeContent={<GenericEditableDateFieldDisplayMode />}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function GenericEditableNumberField() {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditableNumberFieldEditMode />}
|
editModeContent={<GenericEditableNumberFieldEditMode />}
|
||||||
displayModeContent={fieldValue}
|
displayModeContent={fieldValue}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function GenericEditablePhoneField() {
|
|||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
useEditButton
|
useEditButton
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditablePhoneFieldEditMode />}
|
editModeContent={<GenericEditablePhoneFieldEditMode />}
|
||||||
displayModeContent={<PhoneInputDisplay value={fieldValue} />}
|
displayModeContent={<PhoneInputDisplay value={fieldValue} />}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function GenericEditableRelationField() {
|
|||||||
customEditHotkeyScope={{
|
customEditHotkeyScope={{
|
||||||
scope: RelationPickerHotkeyScope.RelationPicker,
|
scope: RelationPickerHotkeyScope.RelationPicker,
|
||||||
}}
|
}}
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditableRelationFieldEditMode />}
|
editModeContent={<GenericEditableRelationFieldEditMode />}
|
||||||
displayModeContent={<GenericEditableRelationFieldDisplayMode />}
|
displayModeContent={<GenericEditableRelationFieldDisplayMode />}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function GenericEditableTextField() {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditableTextFieldEditMode />}
|
editModeContent={<GenericEditableTextFieldEditMode />}
|
||||||
displayModeContent={fieldValue}
|
displayModeContent={fieldValue}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function GenericEditableURLField() {
|
|||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
useEditButton
|
useEditButton
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
editModeContent={<GenericEditableURLFieldEditMode />}
|
editModeContent={<GenericEditableURLFieldEditMode />}
|
||||||
displayModeContent={<FieldDisplayURL URL={fieldValue} />}
|
displayModeContent={<FieldDisplayURL URL={fieldValue} />}
|
||||||
isDisplayModeContentEmpty={!fieldValue}
|
isDisplayModeContentEmpty={!fieldValue}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function ProbabilityEditableField() {
|
|||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
<RecoilScope SpecificContext={FieldRecoilScopeContext}>
|
||||||
<EditableField
|
<EditableField
|
||||||
iconLabel={currentEditableFieldDefinition.icon}
|
IconLabel={currentEditableFieldDefinition.Icon}
|
||||||
displayModeContent={<ProbabilityEditableFieldEditMode />}
|
displayModeContent={<ProbabilityEditableFieldEditMode />}
|
||||||
displayModeContentOnly
|
displayModeContentOnly
|
||||||
disableHoverEffect
|
disableHoverEffect
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
import { FieldMetadata, FieldType } from './FieldMetadata';
|
import { FieldMetadata, FieldType } from './FieldMetadata';
|
||||||
|
|
||||||
export type FieldDefinition<T extends FieldMetadata | unknown> = {
|
export type FieldDefinition<T extends FieldMetadata | unknown> = {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: JSX.Element;
|
Icon?: IconComponent;
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
metadata: T;
|
metadata: T;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
|
||||||
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & (
|
|||||||
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon?: JSX.Element;
|
Icon?: IconComponent;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
metadata: T;
|
metadata: T;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
|
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
import { parseDate } from '~/utils/date-utils';
|
import { parseDate } from '~/utils/date-utils';
|
||||||
@@ -7,13 +8,13 @@ import { parseDate } from '~/utils/date-utils';
|
|||||||
import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
|
import { EditableFieldEditModeDate } from './EditableFieldEditModeDate';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
icon?: React.ReactNode;
|
Icon?: IconComponent;
|
||||||
label?: string;
|
label?: string;
|
||||||
value: string | null | undefined;
|
value: string | null | undefined;
|
||||||
onSubmit?: (newValue: string) => void;
|
onSubmit?: (newValue: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
|
export function DateEditableField({ Icon, value, label, onSubmit }: OwnProps) {
|
||||||
async function handleChange(newValue: string) {
|
async function handleChange(newValue: string) {
|
||||||
onSubmit?.(newValue);
|
onSubmit?.(newValue);
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@ export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
|
|||||||
<EditableField
|
<EditableField
|
||||||
// onSubmit={handleSubmit}
|
// onSubmit={handleSubmit}
|
||||||
// onCancel={handleCancel}
|
// onCancel={handleCancel}
|
||||||
iconLabel={icon}
|
IconLabel={Icon}
|
||||||
label={label}
|
label={label}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<EditableFieldEditModeDate
|
<EditableFieldEditModeDate
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||||
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
|
import { FieldRecoilScopeContext } from '@/ui/editable-field/states/recoil-scope-contexts/FieldRecoilScopeContext';
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
|
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
|
||||||
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
|
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
icon?: React.ReactNode;
|
Icon?: IconComponent;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string | null | undefined;
|
value: string | null | undefined;
|
||||||
onSubmit?: (newValue: string) => void;
|
onSubmit?: (newValue: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PhoneEditableField({
|
export function PhoneEditableField({
|
||||||
icon,
|
Icon,
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@@ -44,7 +45,7 @@ export function PhoneEditableField({
|
|||||||
<EditableField
|
<EditableField
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
iconLabel={icon}
|
IconLabel={Icon}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<TextInputEdit
|
<TextInputEdit
|
||||||
placeholder={placeholder ?? ''}
|
placeholder={placeholder ?? ''}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ const meta: Meta<typeof DateEditableField> = {
|
|||||||
component: DateEditableField,
|
component: DateEditableField,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
icon: {
|
Icon: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
mapping: {
|
mapping: {
|
||||||
true: <IconCalendar />,
|
true: IconCalendar,
|
||||||
false: undefined,
|
false: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -21,7 +21,7 @@ const meta: Meta<typeof DateEditableField> = {
|
|||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
value: new Date().toISOString(),
|
value: new Date().toISOString(),
|
||||||
icon: true,
|
Icon: IconCalendar,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ const meta: Meta<typeof PhoneEditableField> = {
|
|||||||
component: PhoneEditableField,
|
component: PhoneEditableField,
|
||||||
decorators: [ComponentWithRouterDecorator],
|
decorators: [ComponentWithRouterDecorator],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
icon: {
|
Icon: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
mapping: {
|
mapping: {
|
||||||
true: <IconPhone />,
|
true: IconPhone,
|
||||||
false: undefined,
|
false: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
value: '+33714446494',
|
value: '+33714446494',
|
||||||
icon: true,
|
Icon: IconPhone,
|
||||||
placeholder: 'Phone',
|
placeholder: 'Phone',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Context } from 'react';
|
import { Context } from 'react';
|
||||||
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
@@ -43,8 +43,9 @@ export function FilterDropdownFilterSelect({
|
|||||||
return (
|
return (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{availableFilters.map((availableFilter, index) => (
|
{availableFilters.map((availableFilter, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItem
|
||||||
key={`select-filter-${index}`}
|
key={`select-filter-${index}`}
|
||||||
|
testId={`select-filter-${index}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterDefinitionUsedInDropdown(availableFilter);
|
setFilterDefinitionUsedInDropdown(availableFilter);
|
||||||
|
|
||||||
@@ -58,10 +59,9 @@ export function FilterDropdownFilterSelect({
|
|||||||
|
|
||||||
setFilterDropdownSearchInput('');
|
setFilterDropdownSearchInput('');
|
||||||
}}
|
}}
|
||||||
>
|
LeftIcon={availableFilter.Icon}
|
||||||
{availableFilter.icon}
|
text={availableFilter.label}
|
||||||
{availableFilter.label}
|
/>
|
||||||
</DropdownMenuSelectableItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Context } from 'react';
|
import { Context } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { IconChevronDown } from '@/ui/icon';
|
import { IconChevronDown } from '@/ui/icon';
|
||||||
@@ -14,8 +13,6 @@ export function FilterDropdownOperandButton({
|
|||||||
}: {
|
}: {
|
||||||
context: Context<string | null>;
|
context: Context<string | null>;
|
||||||
}) {
|
}) {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [selectedOperandInDropdown] = useRecoilScopedState(
|
const [selectedOperandInDropdown] = useRecoilScopedState(
|
||||||
selectedOperandInDropdownScopedState,
|
selectedOperandInDropdownScopedState,
|
||||||
context,
|
context,
|
||||||
@@ -36,7 +33,7 @@ export function FilterDropdownOperandButton({
|
|||||||
return (
|
return (
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
key={'selected-filter-operand'}
|
key={'selected-filter-operand'}
|
||||||
endIcon={<IconChevronDown size={theme.icon.size.md} />}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsFilterDropdownOperandSelectUnfolded(true)}
|
onClick={() => setIsFilterDropdownOperandSelectUnfolded(true)}
|
||||||
>
|
>
|
||||||
{getOperandLabel(selectedOperandInDropdown)}
|
{getOperandLabel(selectedOperandInDropdown)}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Context } from 'react';
|
import { Context } from 'react';
|
||||||
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
|
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
|
||||||
@@ -66,14 +66,13 @@ export function FilterDropdownOperandSelect({
|
|||||||
return (
|
return (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{operandsForFilterType.map((filterOperand, index) => (
|
{operandsForFilterType.map((filterOperand, index) => (
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
key={`select-filter-operand-${index}`}
|
key={`select-filter-operand-${index}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOperangeChange(filterOperand);
|
handleOperangeChange(filterOperand);
|
||||||
}}
|
}}
|
||||||
>
|
text={getOperandLabel(filterOperand)}
|
||||||
{getOperandLabel(filterOperand)}
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -158,14 +158,12 @@ function SortAndFilterBar<SortField>({
|
|||||||
return (
|
return (
|
||||||
<SortOrFilterChip
|
<SortOrFilterChip
|
||||||
key={sort.key}
|
key={sort.key}
|
||||||
|
testId={sort.key}
|
||||||
labelValue={sort.label}
|
labelValue={sort.label}
|
||||||
id={sort.key}
|
Icon={
|
||||||
icon={
|
sort.order === 'desc'
|
||||||
sort.order === 'desc' ? (
|
? IconArrowNarrowDown
|
||||||
<IconArrowNarrowDown size={theme.icon.size.md} />
|
: IconArrowNarrowUp
|
||||||
) : (
|
|
||||||
<IconArrowNarrowUp size={theme.icon.size.md} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
isSort
|
isSort
|
||||||
onRemove={() => onRemoveSort(sort.key)}
|
onRemove={() => onRemoveSort(sort.key)}
|
||||||
@@ -181,12 +179,12 @@ function SortAndFilterBar<SortField>({
|
|||||||
return (
|
return (
|
||||||
<SortOrFilterChip
|
<SortOrFilterChip
|
||||||
key={filter.key}
|
key={filter.key}
|
||||||
|
testId={filter.key}
|
||||||
labelKey={filter.label}
|
labelKey={filter.label}
|
||||||
labelValue={`${getOperandLabelShort(filter.operand)} ${
|
labelValue={`${getOperandLabelShort(filter.operand)} ${
|
||||||
filter.displayValue
|
filter.displayValue
|
||||||
}`}
|
}`}
|
||||||
id={filter.key}
|
Icon={filter.Icon}
|
||||||
icon={filter.icon}
|
|
||||||
onRemove={() => {
|
onRemove={() => {
|
||||||
removeFilter(filter.key);
|
removeFilter(filter.key);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Context, useCallback, useState } from 'react';
|
import { Context, useCallback, useState } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconChevronDown } from '@/ui/icon';
|
import { IconChevronDown } from '@/ui/icon';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
|
||||||
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
||||||
import { SelectedSortType, SortType } from '../types/interface';
|
import { SelectedSortType, SortType } from '../types/interface';
|
||||||
@@ -30,8 +28,6 @@ export function SortDropdownButton<SortField>({
|
|||||||
onSortSelect,
|
onSortSelect,
|
||||||
HotkeyScope,
|
HotkeyScope,
|
||||||
}: OwnProps<SortField>) {
|
}: OwnProps<SortField>) {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||||
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
||||||
const [selectedSortDirection, setSelectedSortDirection] =
|
const [selectedSortDirection, setSelectedSortDirection] =
|
||||||
@@ -74,21 +70,20 @@ export function SortDropdownButton<SortField>({
|
|||||||
{isOptionUnfolded ? (
|
{isOptionUnfolded ? (
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItem
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedSortDirection(option);
|
setSelectedSortDirection(option);
|
||||||
setIsOptionUnfolded(false);
|
setIsOptionUnfolded(false);
|
||||||
}}
|
}}
|
||||||
>
|
text={option === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
{option === 'asc' ? 'Ascending' : 'Descending'}
|
/>
|
||||||
</DropdownMenuSelectableItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
endIcon={<IconChevronDown size={theme.icon.size.md} />}
|
EndIcon={IconChevronDown}
|
||||||
onClick={() => setIsOptionUnfolded(true)}
|
onClick={() => setIsOptionUnfolded(true)}
|
||||||
>
|
>
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
@@ -97,13 +92,13 @@ export function SortDropdownButton<SortField>({
|
|||||||
|
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{availableSorts.map((sort, index) => (
|
{availableSorts.map((sort, index) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItem
|
||||||
|
testId={`select-sort-${index}`}
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => handleAddSort(sort)}
|
onClick={() => handleAddSort(sort)}
|
||||||
>
|
LeftIcon={sort.Icon}
|
||||||
{sort.icon}
|
text={sort.label}
|
||||||
<OverflowingTextWithTooltip text={sort.label} />
|
/>
|
||||||
</DropdownMenuSelectableItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconX } from '@/ui/icon/index';
|
import { IconX } from '@/ui/icon/index';
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
id: string;
|
|
||||||
labelKey?: string;
|
labelKey?: string;
|
||||||
labelValue: string;
|
labelValue: string;
|
||||||
icon: ReactNode;
|
Icon?: IconComponent;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
isSort?: boolean;
|
isSort?: boolean;
|
||||||
|
testId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyledChipProps = {
|
type StyledChipProps = {
|
||||||
@@ -55,20 +55,24 @@ const StyledLabelKey = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function SortOrFilterChip({
|
function SortOrFilterChip({
|
||||||
id,
|
|
||||||
labelKey,
|
labelKey,
|
||||||
labelValue,
|
labelValue,
|
||||||
icon,
|
Icon,
|
||||||
onRemove,
|
onRemove,
|
||||||
isSort,
|
isSort,
|
||||||
|
testId,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<StyledChip isSort={isSort}>
|
<StyledChip isSort={isSort}>
|
||||||
<StyledIcon>{icon}</StyledIcon>
|
{Icon && (
|
||||||
|
<StyledIcon>
|
||||||
|
<Icon />
|
||||||
|
</StyledIcon>
|
||||||
|
)}
|
||||||
{labelKey && <StyledLabelKey>{labelKey}</StyledLabelKey>}
|
{labelKey && <StyledLabelKey>{labelKey}</StyledLabelKey>}
|
||||||
{labelValue}
|
{labelValue}
|
||||||
<StyledDelete onClick={onRemove} data-testid={'remove-icon-' + id}>
|
<StyledDelete onClick={onRemove} data-testid={'remove-icon-' + testId}>
|
||||||
<IconX size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
<IconX size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
||||||
</StyledDelete>
|
</StyledDelete>
|
||||||
</StyledChip>
|
</StyledChip>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
import { FilterType } from './FilterType';
|
import { FilterType } from './FilterType';
|
||||||
|
|
||||||
export type FilterDefinition = {
|
export type FilterDefinition = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: JSX.Element;
|
Icon: IconComponent;
|
||||||
type: FilterType;
|
type: FilterType;
|
||||||
entitySelectComponent?: JSX.Element;
|
entitySelectComponent?: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ReactNode } from 'react';
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
import { SortOrder as Order_By } from '~/generated/graphql';
|
import { SortOrder as Order_By } from '~/generated/graphql';
|
||||||
|
|
||||||
export type SortType<OrderByTemplate> = {
|
export type SortType<OrderByTemplate> = {
|
||||||
label: string;
|
label: string;
|
||||||
key: string;
|
key: string;
|
||||||
icon?: ReactNode;
|
Icon?: IconComponent;
|
||||||
orderByTemplate?: (order: Order_By) => OrderByTemplate[];
|
orderByTemplate?: (order: Order_By) => OrderByTemplate[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 { useRef } from 'react';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem';
|
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||||
@@ -81,26 +81,25 @@ export function MultipleEntitySelect<
|
|||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
{entitiesInDropdown?.map((entity) => (
|
{entitiesInDropdown?.map((entity) => (
|
||||||
<DropdownMenuCheckableItem
|
<MenuItemMultiSelectAvatar
|
||||||
key={entity.id}
|
key={entity.id}
|
||||||
checked={value[entity.id]}
|
selected={value[entity.id]}
|
||||||
onChange={(newCheckedValue) =>
|
onSelectChange={(newCheckedValue) =>
|
||||||
onChange({ ...value, [entity.id]: newCheckedValue })
|
onChange({ ...value, [entity.id]: newCheckedValue })
|
||||||
}
|
}
|
||||||
>
|
avatar={
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={entity.avatarUrl}
|
avatarUrl={entity.avatarUrl}
|
||||||
colorId={entity.id}
|
colorId={entity.id}
|
||||||
placeholder={entity.name}
|
placeholder={entity.name}
|
||||||
size="md"
|
size="md"
|
||||||
type={entity.avatarType ?? 'rounded'}
|
type={entity.avatarType ?? 'rounded'}
|
||||||
/>
|
/>
|
||||||
{entity.name}
|
}
|
||||||
</DropdownMenuCheckableItem>
|
text={entity.name}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
{entitiesInDropdown?.length === 0 && (
|
{entitiesInDropdown?.length === 0 && <MenuItem text="No result" />}
|
||||||
<DropdownMenuItem>No result</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
|
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconPlus } from '@/ui/icon';
|
import { IconPlus } from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
@@ -39,8 +38,6 @@ export function SingleEntitySelect<
|
|||||||
}) {
|
}) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
|
const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch();
|
||||||
|
|
||||||
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
const showCreateButton = isDefined(onCreate) && searchFilter !== '';
|
||||||
@@ -76,10 +73,7 @@ export function SingleEntitySelect<
|
|||||||
{showCreateButton && (
|
{showCreateButton && (
|
||||||
<>
|
<>
|
||||||
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
<StyledDropdownMenuItemsContainer hasMaxHeight>
|
||||||
<DropdownMenuItem onClick={onCreate}>
|
<MenuItem onClick={onCreate} LeftIcon={IconPlus} text="Add New" />
|
||||||
<IconPlus size={theme.icon.size.md} />
|
|
||||||
Add New
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { IconBuildingSkyscraper, IconUserCircle } from '@/ui/icon';
|
import { IconBuildingSkyscraper, IconUserCircle } from '@/ui/icon';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
@@ -76,43 +74,44 @@ export function SingleEntitySelectBase<
|
|||||||
entitiesInDropdown = entitiesInDropdown.filter((entity) =>
|
entitiesInDropdown = entitiesInDropdown.filter((entity) =>
|
||||||
isNonEmptyString(entity.name.trim()),
|
isNonEmptyString(entity.name.trim()),
|
||||||
);
|
);
|
||||||
const theme = useTheme();
|
|
||||||
|
const NoUserIcon =
|
||||||
|
noUser?.entityType === Entity.User
|
||||||
|
? IconUserCircle
|
||||||
|
: IconBuildingSkyscraper;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdownMenuItemsContainer ref={containerRef} hasMaxHeight>
|
<StyledDropdownMenuItemsContainer ref={containerRef} hasMaxHeight>
|
||||||
{noUser && (
|
{noUser && (
|
||||||
<DropdownMenuItem onClick={() => onEntitySelected(noUser)}>
|
<MenuItem
|
||||||
{noUser.entityType === Entity.User ? (
|
onClick={() => onEntitySelected(noUser)}
|
||||||
<IconUserCircle size={theme.icon.size.md} />
|
LeftIcon={NoUserIcon}
|
||||||
) : (
|
text={noUser.name}
|
||||||
<IconBuildingSkyscraper
|
/>
|
||||||
size={theme.icon.size.md}
|
|
||||||
></IconBuildingSkyscraper>
|
|
||||||
)}
|
|
||||||
{noUser.name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
)}
|
||||||
{entities.loading ? (
|
{entities.loading ? (
|
||||||
<DropdownMenuSkeletonItem />
|
<DropdownMenuSkeletonItem />
|
||||||
) : entitiesInDropdown.length === 0 ? (
|
) : entitiesInDropdown.length === 0 ? (
|
||||||
<DropdownMenuItem>No result</DropdownMenuItem>
|
<MenuItem text="No result" />
|
||||||
) : (
|
) : (
|
||||||
entitiesInDropdown?.map((entity, index) => (
|
entitiesInDropdown?.map((entity) => (
|
||||||
<DropdownMenuSelectableItem
|
<MenuItemSelectAvatar
|
||||||
key={entity.id}
|
key={entity.id}
|
||||||
|
testId="menu-item"
|
||||||
selected={entities.selectedEntity?.id === entity.id}
|
selected={entities.selectedEntity?.id === entity.id}
|
||||||
hovered={hoveredIndex === index}
|
|
||||||
onClick={() => onEntitySelected(entity)}
|
onClick={() => onEntitySelected(entity)}
|
||||||
>
|
text={entity.name}
|
||||||
<Avatar
|
hovered={hoveredIndex === entitiesInDropdown.indexOf(entity)}
|
||||||
avatarUrl={entity.avatarUrl}
|
avatar={
|
||||||
colorId={entity.id}
|
<Avatar
|
||||||
placeholder={entity.name}
|
avatarUrl={entity.avatarUrl}
|
||||||
size="md"
|
colorId={entity.id}
|
||||||
type={entity.avatarType ?? 'rounded'}
|
placeholder={entity.name}
|
||||||
/>
|
size="md"
|
||||||
<OverflowingTextWithTooltip text={entity.name} />
|
type={entity.avatarType ?? 'rounded'}
|
||||||
</DropdownMenuSelectableItem>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
|||||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index';
|
import { IconCheckbox, IconNotes, IconPlus } from '@/ui/icon/index';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { ActivityType } from '~/generated/graphql';
|
import { ActivityType } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@@ -50,20 +50,18 @@ export function ShowPageAddButton({
|
|||||||
<StyledDropdownMenuItemsContainer
|
<StyledDropdownMenuItemsContainer
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
onClick={() => handleSelect(ActivityType.Note)}
|
onClick={() => handleSelect(ActivityType.Note)}
|
||||||
accent="regular"
|
accent="default"
|
||||||
>
|
LeftIcon={IconNotes}
|
||||||
<IconNotes size={16} />
|
text="Note"
|
||||||
Note
|
/>
|
||||||
</DropdownMenuItem>
|
<MenuItem
|
||||||
<DropdownMenuItem
|
onClick={() => handleSelect(ActivityType.Note)}
|
||||||
onClick={() => handleSelect(ActivityType.Task)}
|
accent="default"
|
||||||
accent="regular"
|
LeftIcon={IconCheckbox}
|
||||||
>
|
text="Task"
|
||||||
<IconCheckbox size={16} />
|
/>
|
||||||
Task
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledDropdownMenu>
|
</StyledDropdownMenu>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MouseEvent } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
||||||
@@ -10,24 +11,26 @@ import { MenuItemAccent } from '../types/MenuItemAccent';
|
|||||||
|
|
||||||
export type MenuItemIconButton = {
|
export type MenuItemIconButton = {
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
onClick: () => void;
|
onClick?: (event: MouseEvent<any>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuItemProps = {
|
export type MenuItemProps = {
|
||||||
LeftIcon?: IconComponent;
|
LeftIcon?: IconComponent | null;
|
||||||
accent: MenuItemAccent;
|
accent?: MenuItemAccent;
|
||||||
text: string;
|
text: string;
|
||||||
iconButtons?: MenuItemIconButton[];
|
iconButtons?: MenuItemIconButton[];
|
||||||
className: string;
|
className?: string;
|
||||||
|
testId?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MenuItem({
|
export function MenuItem({
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
accent,
|
accent = 'default',
|
||||||
text,
|
text,
|
||||||
iconButtons,
|
iconButtons,
|
||||||
className,
|
className,
|
||||||
|
testId,
|
||||||
onClick,
|
onClick,
|
||||||
}: MenuItemProps) {
|
}: MenuItemProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -35,8 +38,13 @@ export function MenuItem({
|
|||||||
const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0;
|
const showIconButtons = Array.isArray(iconButtons) && iconButtons.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledMenuItemBase onClick={onClick} className={className} accent={accent}>
|
<StyledMenuItemBase
|
||||||
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
data-testid={testId ?? undefined}
|
||||||
|
onClick={onClick}
|
||||||
|
className={className}
|
||||||
|
accent={accent}
|
||||||
|
>
|
||||||
|
<MenuItemLeftContent LeftIcon={LeftIcon ?? undefined} text={text} />
|
||||||
{showIconButtons && (
|
{showIconButtons && (
|
||||||
<FloatingIconButtonGroup>
|
<FloatingIconButtonGroup>
|
||||||
{iconButtons?.map(({ Icon, onClick }, index) => (
|
{iconButtons?.map(({ Icon, onClick }, index) => (
|
||||||
|
|||||||
@@ -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;
|
LeftIcon?: IconComponent;
|
||||||
text: string;
|
text: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
className: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MenuItemNavigate({
|
export function MenuItemNavigate({
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import { IconComponent } from '@/ui/icon/types/IconComponent';
|
|||||||
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
|
import { MenuItemLeftContent } from '../internals/components/MenuItemLeftContent';
|
||||||
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
|
import { StyledMenuItemBase } from '../internals/components/StyledMenuItemBase';
|
||||||
|
|
||||||
const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>`
|
export const StyledMenuItemSelect = styled(StyledMenuItemBase)<{
|
||||||
${({ theme, selected }) => {
|
selected: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
hovered?: boolean;
|
||||||
|
}>`
|
||||||
|
${({ theme, selected, disabled, hovered }) => {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
return css`
|
return css`
|
||||||
background: ${theme.background.transparent.light};
|
background: ${theme.background.transparent.light};
|
||||||
@@ -16,16 +20,33 @@ const StyledMenuItemSelect = styled(StyledMenuItemBase)<{ selected: boolean }>`
|
|||||||
background: ${theme.background.transparent.medium};
|
background: ${theme.background.transparent.medium};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
} else if (disabled) {
|
||||||
|
return css`
|
||||||
|
background: inherit;
|
||||||
|
&:hover {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: ${theme.font.color.tertiary};
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
`;
|
||||||
|
} else if (hovered) {
|
||||||
|
return css`
|
||||||
|
background: ${theme.background.transparent.light};
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
LeftIcon?: IconComponent;
|
LeftIcon: IconComponent | null | undefined;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
className: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
hovered?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MenuItemSelect({
|
export function MenuItemSelect({
|
||||||
@@ -34,6 +55,8 @@ export function MenuItemSelect({
|
|||||||
selected,
|
selected,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
|
disabled,
|
||||||
|
hovered,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -42,6 +65,8 @@ export function MenuItemSelect({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={className}
|
className={className}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
disabled={disabled}
|
||||||
|
hovered={hovered}
|
||||||
>
|
>
|
||||||
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
<MenuItemLeftContent LeftIcon={LeftIcon} text={text} />
|
||||||
{selected && <IconCheck size={theme.icon.size.sm} />}
|
{selected && <IconCheck size={theme.icon.size.sm} />}
|
||||||
|
|||||||
@@ -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 { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
StyledMenuItemLabel,
|
StyledMenuItemLabel,
|
||||||
@@ -8,7 +9,7 @@ import {
|
|||||||
} from './StyledMenuItemBase';
|
} from './StyledMenuItemBase';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
LeftIcon?: IconComponent;
|
LeftIcon: IconComponent | null | undefined;
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,7 +19,9 @@ export function MenuItemLeftContent({ LeftIcon, text }: OwnProps) {
|
|||||||
return (
|
return (
|
||||||
<StyledMenuItemLeftContent>
|
<StyledMenuItemLeftContent>
|
||||||
{LeftIcon && <LeftIcon size={theme.icon.size.md} />}
|
{LeftIcon && <LeftIcon size={theme.icon.size.md} />}
|
||||||
<StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>{text}</StyledMenuItemLabel>
|
<StyledMenuItemLabel hasLeftIcon={!!LeftIcon}>
|
||||||
|
<OverflowingTextWithTooltip text={text} />
|
||||||
|
</StyledMenuItemLabel>
|
||||||
</StyledMenuItemLeftContent>
|
</StyledMenuItemLeftContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
@@ -29,8 +29,6 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
|
|||||||
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
padding: var(--vertical-padding) var(--horizontal-padding);
|
|
||||||
|
|
||||||
${hoverBackground};
|
${hoverBackground};
|
||||||
|
|
||||||
${({ theme, accent }) => {
|
${({ theme, accent }) => {
|
||||||
@@ -43,6 +41,11 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
case 'placeholder': {
|
||||||
|
return css`
|
||||||
|
color: ${theme.font.color.tertiary};
|
||||||
|
`;
|
||||||
|
}
|
||||||
case 'default':
|
case 'default':
|
||||||
default: {
|
default: {
|
||||||
return css`
|
return css`
|
||||||
@@ -52,7 +55,9 @@ export const StyledMenuItemBase = styled.li<MenuItemBaseProps>`
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
padding: var(--vertical-padding) var(--horizontal-padding);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
width: calc(100% - 2 * var(--horizontal-padding));
|
width: calc(100% - 2 * var(--horizontal-padding));
|
||||||
|
|||||||
@@ -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 styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
viewName: string;
|
viewName: string;
|
||||||
viewIcon?: ReactNode;
|
ViewIcon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledTitle = styled.div`
|
const StyledTitle = styled.div`
|
||||||
@@ -32,10 +34,13 @@ const StyledText = styled.span`
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function ColumnHead({ viewName, viewIcon }: OwnProps) {
|
export function ColumnHead({ viewName, ViewIcon }: OwnProps) {
|
||||||
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<StyledTitle>
|
<StyledTitle>
|
||||||
<StyledIcon>{viewIcon}</StyledIcon>
|
<StyledIcon>
|
||||||
|
{ViewIcon && <ViewIcon size={theme.icon.size.md} />}
|
||||||
|
</StyledIcon>
|
||||||
<StyledText>{viewName}</StyledText>
|
<StyledText>{viewName}</StyledText>
|
||||||
</StyledTitle>
|
</StyledTitle>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { cloneElement, type ComponentProps, useCallback, useRef } from 'react';
|
import { type ComponentProps, useCallback, useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
import { IconPlus } from '@/ui/icon';
|
import { IconPlus } from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
|
|
||||||
@@ -31,7 +29,6 @@ export const EntityTableColumnMenu = ({
|
|||||||
...props
|
...props
|
||||||
}: EntityTableColumnMenuProps) => {
|
}: EntityTableColumnMenuProps) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const hiddenTableColumns = useRecoilScopedValue(
|
const hiddenTableColumns = useRecoilScopedValue(
|
||||||
hiddenTableColumnsScopedSelector,
|
hiddenTableColumnsScopedSelector,
|
||||||
@@ -57,22 +54,17 @@ export const EntityTableColumnMenu = ({
|
|||||||
<StyledColumnMenu {...props} ref={ref}>
|
<StyledColumnMenu {...props} ref={ref}>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{hiddenTableColumns.map((column) => (
|
{hiddenTableColumns.map((column) => (
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
key={column.key}
|
key={column.key}
|
||||||
actions={[
|
iconButtons={[
|
||||||
<IconButton
|
{
|
||||||
key={`add-${column.key}`}
|
Icon: IconPlus,
|
||||||
icon={<IconPlus size={theme.icon.size.sm} />}
|
onClick: () => handleAddColumn(column),
|
||||||
onClick={() => handleAddColumn(column)}
|
},
|
||||||
/>,
|
|
||||||
]}
|
]}
|
||||||
>
|
LeftIcon={column.Icon}
|
||||||
{column.icon &&
|
text={column.name}
|
||||||
cloneElement(column.icon, {
|
/>
|
||||||
size: theme.icon.size.md,
|
|
||||||
})}
|
|
||||||
{column.name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</StyledColumnMenu>
|
</StyledColumnMenu>
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export function EntityTableHeader() {
|
|||||||
COLUMN_MIN_WIDTH,
|
COLUMN_MIN_WIDTH,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ColumnHead viewName={column.name} viewIcon={column.icon} />
|
<ColumnHead viewName={column.name} ViewIcon={column.Icon} />
|
||||||
<StyledResizeHandler
|
<StyledResizeHandler
|
||||||
className="cursor-col-resize"
|
className="cursor-col-resize"
|
||||||
role="separator"
|
role="separator"
|
||||||
|
|||||||
@@ -1,35 +1,25 @@
|
|||||||
import { type FormEvent, useCallback, useRef, useState } from 'react';
|
import { type FormEvent, useCallback, useRef, useState } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
|
||||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
|
||||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
|
||||||
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
|
||||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||||
import {
|
import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon';
|
||||||
IconChevronLeft,
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
IconFileImport,
|
|
||||||
IconMinus,
|
|
||||||
IconPlus,
|
|
||||||
IconTag,
|
|
||||||
} from '@/ui/icon';
|
|
||||||
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
|
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
|
|
||||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
|
||||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
|
import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
|
||||||
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
|
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
|
||||||
@@ -41,10 +31,9 @@ import {
|
|||||||
tableViewsByIdState,
|
tableViewsByIdState,
|
||||||
tableViewsState,
|
tableViewsState,
|
||||||
} from '../../states/tableViewsState';
|
} from '../../states/tableViewsState';
|
||||||
import type { ColumnDefinition } from '../../types/ColumnDefinition';
|
|
||||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||||
|
|
||||||
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
|
import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection';
|
||||||
|
|
||||||
type TableOptionsDropdownButtonProps = {
|
type TableOptionsDropdownButtonProps = {
|
||||||
onViewsChange?: (views: TableView[]) => void;
|
onViewsChange?: (views: TableView[]) => void;
|
||||||
@@ -59,8 +48,6 @@ export function TableOptionsDropdownContent({
|
|||||||
onViewsChange,
|
onViewsChange,
|
||||||
onImport,
|
onImport,
|
||||||
}: TableOptionsDropdownButtonProps) {
|
}: TableOptionsDropdownButtonProps) {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||||
|
|
||||||
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
|
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
|
||||||
@@ -87,33 +74,6 @@ export function TableOptionsDropdownContent({
|
|||||||
TableRecoilScopeContext,
|
TableRecoilScopeContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handleColumnVisibilityChange } = useTableColumns();
|
|
||||||
|
|
||||||
const renderFieldActions = useCallback(
|
|
||||||
(column: ColumnDefinition<ViewFieldMetadata>) =>
|
|
||||||
// Do not allow hiding last visible column
|
|
||||||
!column.isVisible || visibleTableColumns.length > 1
|
|
||||||
? [
|
|
||||||
<IconButton
|
|
||||||
key={`action-${column.key}`}
|
|
||||||
icon={
|
|
||||||
column.isVisible ? (
|
|
||||||
<IconMinus size={theme.icon.size.sm} />
|
|
||||||
) : (
|
|
||||||
<IconPlus size={theme.icon.size.sm} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() => handleColumnVisibilityChange(column)}
|
|
||||||
/>,
|
|
||||||
]
|
|
||||||
: undefined,
|
|
||||||
[
|
|
||||||
handleColumnVisibilityChange,
|
|
||||||
theme.icon.size.sm,
|
|
||||||
visibleTableColumns.length,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const resetViewEditMode = useCallback(() => {
|
const resetViewEditMode = useCallback(() => {
|
||||||
setTableViewEditMode({ mode: undefined, viewId: undefined });
|
setTableViewEditMode({ mode: undefined, viewId: undefined });
|
||||||
|
|
||||||
@@ -232,17 +192,17 @@ export function TableOptionsDropdownContent({
|
|||||||
)}
|
)}
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
onClick={() => handleSelectOption(Option.Properties)}
|
onClick={() => handleSelectOption(Option.Properties)}
|
||||||
>
|
LeftIcon={IconTag}
|
||||||
<IconTag size={theme.icon.size.md} />
|
text="Properties"
|
||||||
Properties
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
{onImport && (
|
{onImport && (
|
||||||
<DropdownMenuItem onClick={onImport}>
|
<MenuItem
|
||||||
<IconFileImport size={theme.icon.size.md} />
|
onClick={onImport}
|
||||||
Import
|
LeftIcon={IconFileImport}
|
||||||
</DropdownMenuItem>
|
text="Import"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
@@ -250,22 +210,20 @@ export function TableOptionsDropdownContent({
|
|||||||
{selectedOption === Option.Properties && (
|
{selectedOption === Option.Properties && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader
|
<DropdownMenuHeader
|
||||||
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
StartIcon={IconChevronLeft}
|
||||||
onClick={resetSelectedOption}
|
onClick={resetSelectedOption}
|
||||||
>
|
>
|
||||||
Properties
|
Properties
|
||||||
</DropdownMenuHeader>
|
</DropdownMenuHeader>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<TableOptionsDropdownSection
|
<TableOptionsDropdownColumnVisibility
|
||||||
renderActions={renderFieldActions}
|
|
||||||
title="Visible"
|
title="Visible"
|
||||||
columns={visibleTableColumns}
|
columns={visibleTableColumns}
|
||||||
/>
|
/>
|
||||||
{hiddenTableColumns.length > 0 && (
|
{hiddenTableColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<TableOptionsDropdownSection
|
<TableOptionsDropdownColumnVisibility
|
||||||
renderActions={renderFieldActions}
|
|
||||||
title="Hidden"
|
title="Hidden"
|
||||||
columns={hiddenTableColumns}
|
columns={hiddenTableColumns}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
import { cloneElement } from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuItemProps,
|
|
||||||
} from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
|
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
|
||||||
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||||
|
import { IconMinus, IconPlus } from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
|
|
||||||
|
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||||
import type { ColumnDefinition } from '../../types/ColumnDefinition';
|
import type { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||||
|
|
||||||
type TableOptionsDropdownSectionProps = {
|
type OwnProps = {
|
||||||
renderActions: (
|
|
||||||
column: ColumnDefinition<ViewFieldMetadata>,
|
|
||||||
) => DropdownMenuItemProps['actions'];
|
|
||||||
title: string;
|
title: string;
|
||||||
columns: ColumnDefinition<ViewFieldMetadata>[];
|
columns: ColumnDefinition<ViewFieldMetadata>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TableOptionsDropdownSection({
|
export function TableOptionsDropdownColumnVisibility({
|
||||||
renderActions,
|
|
||||||
title,
|
title,
|
||||||
columns,
|
columns,
|
||||||
}: TableOptionsDropdownSectionProps) {
|
}: OwnProps) {
|
||||||
const theme = useTheme();
|
const { handleColumnVisibilityChange } = useTableColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
|
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<DropdownMenuItem key={column.key} actions={renderActions(column)}>
|
<MenuItem
|
||||||
{column.icon &&
|
key={column.key}
|
||||||
cloneElement(column.icon, {
|
LeftIcon={column.Icon}
|
||||||
size: theme.icon.size.md,
|
iconButtons={[
|
||||||
})}
|
{
|
||||||
{column.name}
|
Icon: column.isVisible ? IconMinus : IconPlus,
|
||||||
</DropdownMenuItem>
|
onClick: () => handleColumnVisibilityChange(column),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
text={column.name}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { Button } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
|
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
|
||||||
@@ -17,6 +15,7 @@ import { canPersistFiltersScopedSelector } from '@/ui/filter-n-sort/states/selec
|
|||||||
import { canPersistSortsScopedSelector } from '@/ui/filter-n-sort/states/selectors/canPersistSortsScopedSelector';
|
import { canPersistSortsScopedSelector } from '@/ui/filter-n-sort/states/selectors/canPersistSortsScopedSelector';
|
||||||
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
|
||||||
import { IconChevronDown, IconPlus } from '@/ui/icon';
|
import { IconChevronDown, IconPlus } from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
@@ -45,8 +44,6 @@ export const TableUpdateViewButtonGroup = ({
|
|||||||
onViewSubmit,
|
onViewSubmit,
|
||||||
HotkeyScope,
|
HotkeyScope,
|
||||||
}: TableUpdateViewButtonGroupProps) => {
|
}: TableUpdateViewButtonGroupProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
|
|
||||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||||
@@ -153,10 +150,11 @@ export const TableUpdateViewButtonGroup = ({
|
|||||||
{isDropdownOpen && (
|
{isDropdownOpen && (
|
||||||
<DropdownMenuContainer onClose={handleDropdownClose}>
|
<DropdownMenuContainer onClose={handleDropdownClose}>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem onClick={handleCreateViewButtonClick}>
|
<MenuItem
|
||||||
<IconPlus size={theme.icon.size.md} />
|
onClick={handleCreateViewButtonClick}
|
||||||
Create view
|
LeftIcon={IconPlus}
|
||||||
</DropdownMenuItem>
|
text="Create view"
|
||||||
|
/>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
</DropdownMenuContainer>
|
</DropdownMenuContainer>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||||
@@ -20,6 +18,7 @@ import {
|
|||||||
IconPlus,
|
IconPlus,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
} from '@/ui/icon';
|
} from '@/ui/icon';
|
||||||
|
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||||
import {
|
import {
|
||||||
currentTableViewIdState,
|
currentTableViewIdState,
|
||||||
currentTableViewState,
|
currentTableViewState,
|
||||||
@@ -204,37 +203,35 @@ export const TableViewsDropdownButton = ({
|
|||||||
>
|
>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{tableViews.map((view) => (
|
{tableViews.map((view) => (
|
||||||
<DropdownMenuItem
|
<MenuItem
|
||||||
key={view.id}
|
key={view.id}
|
||||||
actions={[
|
iconButtons={[
|
||||||
<FloatingIconButton
|
{
|
||||||
key="edit"
|
Icon: IconPencil,
|
||||||
onClick={(event) => handleEditViewButtonClick(event, view.id)}
|
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||||
icon={<IconPencil size={theme.icon.size.sm} />}
|
handleEditViewButtonClick(event, view.id),
|
||||||
/>,
|
},
|
||||||
tableViews.length > 1 ? (
|
tableViews.length > 1
|
||||||
<FloatingIconButton
|
? {
|
||||||
key="delete"
|
Icon: IconTrash,
|
||||||
onClick={(event) =>
|
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||||
handleDeleteViewButtonClick(event, view.id)
|
handleDeleteViewButtonClick(event, view.id),
|
||||||
}
|
}
|
||||||
icon={<IconTrash size={theme.icon.size.sm} />}
|
: null,
|
||||||
/>
|
|
||||||
) : null,
|
|
||||||
].filter(assertNotNull)}
|
].filter(assertNotNull)}
|
||||||
onClick={() => handleViewSelect(view.id)}
|
onClick={() => handleViewSelect(view.id)}
|
||||||
>
|
LeftIcon={IconList}
|
||||||
<IconList size={theme.icon.size.md} />
|
text={view.name}
|
||||||
<StyledViewName>{view.name}</StyledViewName>
|
/>
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
))}
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledBoldDropdownMenuItemsContainer>
|
<StyledBoldDropdownMenuItemsContainer>
|
||||||
<DropdownMenuItem onClick={handleAddViewButtonClick}>
|
<MenuItem
|
||||||
<IconPlus size={theme.icon.size.md} />
|
onClick={handleAddViewButtonClick}
|
||||||
Add view
|
LeftIcon={IconPlus}
|
||||||
</DropdownMenuItem>
|
text="Add view"
|
||||||
|
/>
|
||||||
</StyledBoldDropdownMenuItemsContainer>
|
</StyledBoldDropdownMenuItemsContainer>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||||
|
|
||||||
const tagColors = [
|
const tagColors = [
|
||||||
'green',
|
'green',
|
||||||
'turquoise',
|
'turquoise',
|
||||||
@@ -40,7 +42,7 @@ const StyledTag = styled.h3<{
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type TagProps = {
|
export type TagProps = {
|
||||||
color: string;
|
color: ThemeColor;
|
||||||
text: string;
|
text: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,8 +22,22 @@ export const grayScale = {
|
|||||||
gray0: '#ffffff',
|
gray0: '#ffffff',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const color = {
|
export const mainColors = {
|
||||||
yellow: '#ffd338',
|
yellow: '#ffd338',
|
||||||
|
green: '#55ef3c',
|
||||||
|
turquoise: '#15de8f',
|
||||||
|
sky: '#00e0ff',
|
||||||
|
blue: '#1961ed',
|
||||||
|
purple: '#915ffd',
|
||||||
|
pink: '#f54bd0',
|
||||||
|
red: '#f83e3e',
|
||||||
|
orange: '#ff7222',
|
||||||
|
gray: grayScale.gray30,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ThemeColor = keyof typeof mainColors;
|
||||||
|
|
||||||
|
export const secondaryColors = {
|
||||||
yellow80: '#2e2a1a',
|
yellow80: '#2e2a1a',
|
||||||
yellow70: '#453d1e',
|
yellow70: '#453d1e',
|
||||||
yellow60: '#746224',
|
yellow60: '#746224',
|
||||||
@@ -32,7 +46,7 @@ export const color = {
|
|||||||
yellow30: '#ffedaf',
|
yellow30: '#ffedaf',
|
||||||
yellow20: '#fff6d7',
|
yellow20: '#fff6d7',
|
||||||
yellow10: '#fffbeb',
|
yellow10: '#fffbeb',
|
||||||
green: '#55ef3c',
|
|
||||||
green80: '#1d2d1b',
|
green80: '#1d2d1b',
|
||||||
green70: '#23421e',
|
green70: '#23421e',
|
||||||
green60: '#2a5822',
|
green60: '#2a5822',
|
||||||
@@ -41,7 +55,7 @@ export const color = {
|
|||||||
green30: '#ccfac5',
|
green30: '#ccfac5',
|
||||||
green20: '#ddfcd8',
|
green20: '#ddfcd8',
|
||||||
green10: '#eefdec',
|
green10: '#eefdec',
|
||||||
turquoise: '#15de8f',
|
|
||||||
turquoise80: '#172b23',
|
turquoise80: '#172b23',
|
||||||
turquoise70: '#173f2f',
|
turquoise70: '#173f2f',
|
||||||
turquoise60: '#166747',
|
turquoise60: '#166747',
|
||||||
@@ -50,7 +64,7 @@ export const color = {
|
|||||||
turquoise30: '#a1f2d2',
|
turquoise30: '#a1f2d2',
|
||||||
turquoise20: '#d0f8e9',
|
turquoise20: '#d0f8e9',
|
||||||
turquoise10: '#e8fcf4',
|
turquoise10: '#e8fcf4',
|
||||||
sky: '#00e0ff',
|
|
||||||
sky80: '#152b2e',
|
sky80: '#152b2e',
|
||||||
sky70: '#123f45',
|
sky70: '#123f45',
|
||||||
sky60: '#0e6874',
|
sky60: '#0e6874',
|
||||||
@@ -59,7 +73,7 @@ export const color = {
|
|||||||
sky30: '#99f3ff',
|
sky30: '#99f3ff',
|
||||||
sky20: '#ccf9ff',
|
sky20: '#ccf9ff',
|
||||||
sky10: '#e5fcff',
|
sky10: '#e5fcff',
|
||||||
blue: '#1961ed',
|
|
||||||
blue80: '#171e2c',
|
blue80: '#171e2c',
|
||||||
blue70: '#172642',
|
blue70: '#172642',
|
||||||
blue60: '#18356d',
|
blue60: '#18356d',
|
||||||
@@ -68,7 +82,7 @@ export const color = {
|
|||||||
blue30: '#a3c0f8',
|
blue30: '#a3c0f8',
|
||||||
blue20: '#d1dffb',
|
blue20: '#d1dffb',
|
||||||
blue10: '#e8effd',
|
blue10: '#e8effd',
|
||||||
purple: '#915ffd',
|
|
||||||
purple80: '#231e2e',
|
purple80: '#231e2e',
|
||||||
purple70: '#2f2545',
|
purple70: '#2f2545',
|
||||||
purple60: '#483473',
|
purple60: '#483473',
|
||||||
@@ -77,7 +91,7 @@ export const color = {
|
|||||||
purple30: '#d3bffe',
|
purple30: '#d3bffe',
|
||||||
purple20: '#e9dfff',
|
purple20: '#e9dfff',
|
||||||
purple10: '#f4efff',
|
purple10: '#f4efff',
|
||||||
pink: '#f54bd0',
|
|
||||||
pink80: '#2d1c29',
|
pink80: '#2d1c29',
|
||||||
pink70: '#43213c',
|
pink70: '#43213c',
|
||||||
pink60: '#702c61',
|
pink60: '#702c61',
|
||||||
@@ -86,7 +100,7 @@ export const color = {
|
|||||||
pink30: '#fbb7ec',
|
pink30: '#fbb7ec',
|
||||||
pink20: '#fddbf6',
|
pink20: '#fddbf6',
|
||||||
pink10: '#feedfa',
|
pink10: '#feedfa',
|
||||||
red: '#f83e3e',
|
|
||||||
red80: '#2d1b1b',
|
red80: '#2d1b1b',
|
||||||
red70: '#441f1f',
|
red70: '#441f1f',
|
||||||
red60: '#712727',
|
red60: '#712727',
|
||||||
@@ -95,7 +109,7 @@ export const color = {
|
|||||||
red30: '#fcb2b2',
|
red30: '#fcb2b2',
|
||||||
red20: '#fed8d8',
|
red20: '#fed8d8',
|
||||||
red10: '#feecec',
|
red10: '#feecec',
|
||||||
orange: '#ff7222',
|
|
||||||
orange80: '#2e2018',
|
orange80: '#2e2018',
|
||||||
orange70: '#452919',
|
orange70: '#452919',
|
||||||
orange60: '#743b1b',
|
orange60: '#743b1b',
|
||||||
@@ -104,7 +118,7 @@ export const color = {
|
|||||||
orange30: '#ffc7a7',
|
orange30: '#ffc7a7',
|
||||||
orange20: '#ffe3d3',
|
orange20: '#ffe3d3',
|
||||||
orange10: '#fff1e9',
|
orange10: '#fff1e9',
|
||||||
gray: grayScale.gray30,
|
|
||||||
gray80: grayScale.gray70,
|
gray80: grayScale.gray70,
|
||||||
gray70: grayScale.gray65,
|
gray70: grayScale.gray65,
|
||||||
gray60: grayScale.gray55,
|
gray60: grayScale.gray55,
|
||||||
@@ -127,6 +141,11 @@ export const color = {
|
|||||||
blueAccent10: '#f5f9fd',
|
blueAccent10: '#f5f9fd',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const color = {
|
||||||
|
...mainColors,
|
||||||
|
...secondaryColors,
|
||||||
|
};
|
||||||
|
|
||||||
export function rgba(hex: string, alpha: number) {
|
export function rgba(hex: string, alpha: number) {
|
||||||
const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(',');
|
const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(',');
|
||||||
return `rgba(${rgb},${alpha})`;
|
return `rgba(${rgb},${alpha})`;
|
||||||
|
|||||||
@@ -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');
|
const filterButton = await canvas.findByText('Filter');
|
||||||
await userEvent.click(filterButton);
|
await userEvent.click(filterButton);
|
||||||
|
|
||||||
const nameFilterButton = (
|
const nameFilterButton = await canvas.findByTestId('select-filter-0');
|
||||||
await canvas.findAllByTestId('dropdown-menu-item')
|
|
||||||
).find((item) => {
|
|
||||||
return item.textContent === 'Name';
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(nameFilterButton);
|
assert(nameFilterButton);
|
||||||
|
|
||||||
@@ -70,11 +66,9 @@ export const FilterByAccountOwner: Story = {
|
|||||||
const filterButton = await canvas.findByText('Filter');
|
const filterButton = await canvas.findByText('Filter');
|
||||||
await userEvent.click(filterButton);
|
await userEvent.click(filterButton);
|
||||||
|
|
||||||
const accountOwnerFilterButton = (
|
const accountOwnerFilterButton = await canvas.findByTestId(
|
||||||
await canvas.findAllByTestId('dropdown-menu-item')
|
'select-filter-5',
|
||||||
).find((item) => {
|
);
|
||||||
return item.textContent === 'Account owner';
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(accountOwnerFilterButton);
|
assert(accountOwnerFilterButton);
|
||||||
|
|
||||||
@@ -89,11 +83,11 @@ export const FilterByAccountOwner: Story = {
|
|||||||
|
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
|
||||||
const charlesChip = (
|
const charlesChip = (await canvas.findAllByTestId('menu-item')).find(
|
||||||
await canvas.findAllByTestId('dropdown-menu-item')
|
(item) => {
|
||||||
).find((item) => {
|
return item.textContent?.includes('Charles Test');
|
||||||
return item.textContent?.includes('Charles Test');
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
assert(charlesChip);
|
assert(charlesChip);
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,8 @@ export const SortByName: Story = {
|
|||||||
const sortButton = await canvas.findByText('Sort');
|
const sortButton = await canvas.findByText('Sort');
|
||||||
await userEvent.click(sortButton);
|
await userEvent.click(sortButton);
|
||||||
|
|
||||||
const nameSortButton = canvas.getByText('Name', {
|
const nameSortButton = await canvas.findByTestId('select-sort-0');
|
||||||
selector: 'li > div > div',
|
|
||||||
});
|
|
||||||
await userEvent.click(nameSortButton);
|
await userEvent.click(nameSortButton);
|
||||||
|
|
||||||
expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument();
|
expect(await canvas.getByTestId('remove-icon-name')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
IconUsers,
|
IconUsers,
|
||||||
} from '@/ui/icon/index';
|
} from '@/ui/icon/index';
|
||||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { icon } from '@/ui/theme/constants/icon';
|
|
||||||
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
|
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
|
||||||
import { Company } from '~/generated/graphql';
|
import { Company } from '~/generated/graphql';
|
||||||
|
|
||||||
@@ -16,39 +15,37 @@ export const companiesFilters: FilterDefinitionByEntity<Company>[] = [
|
|||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
icon: (
|
Icon: IconBuildingSkyscraper,
|
||||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
|
||||||
),
|
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'employees',
|
key: 'employees',
|
||||||
label: 'Employees',
|
label: 'Employees',
|
||||||
icon: <IconUsers size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconUsers,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'domainName',
|
key: 'domainName',
|
||||||
label: 'URL',
|
label: 'URL',
|
||||||
icon: <IconLink size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconLink,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'address',
|
key: 'address',
|
||||||
label: 'Address',
|
label: 'Address',
|
||||||
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconMap,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconCalendarEvent,
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'accountOwnerId',
|
key: 'accountOwnerId',
|
||||||
label: 'Account owner',
|
label: 'Account owner',
|
||||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconUser,
|
||||||
type: 'entity',
|
type: 'entity',
|
||||||
entitySelectComponent: (
|
entitySelectComponent: (
|
||||||
<FilterDropdownUserSearchSelect context={TableRecoilScopeContext} />
|
<FilterDropdownUserSearchSelect context={TableRecoilScopeContext} />
|
||||||
|
|||||||
@@ -12,26 +12,26 @@ export const availableSorts: SortType<Companies_Order_By>[] = [
|
|||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
icon: <IconBuildingSkyscraper size={16} />,
|
Icon: IconBuildingSkyscraper,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'employees',
|
key: 'employees',
|
||||||
label: 'Employees',
|
label: 'Employees',
|
||||||
icon: <IconUsers size={16} />,
|
Icon: IconUsers,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'domainName',
|
key: 'domainName',
|
||||||
label: 'Url',
|
label: 'Url',
|
||||||
icon: <IconLink size={16} />,
|
Icon: IconLink,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'address',
|
key: 'address',
|
||||||
label: 'Address',
|
label: 'Address',
|
||||||
icon: <IconMap size={16} />,
|
Icon: IconMap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
label: 'Creation',
|
label: 'Creation',
|
||||||
icon: <IconCalendarEvent size={16} />,
|
Icon: IconCalendarEvent,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'domainName',
|
key: 'domainName',
|
||||||
name: 'Domain name',
|
name: 'Domain name',
|
||||||
icon: <IconLink />,
|
Icon: IconLink,
|
||||||
type: 'url',
|
type: 'url',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'domainName',
|
fieldName: 'domainName',
|
||||||
@@ -33,7 +33,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'accountOwner',
|
key: 'accountOwner',
|
||||||
name: 'Account owner',
|
name: 'Account owner',
|
||||||
icon: <IconUserCircle />,
|
Icon: IconUserCircle,
|
||||||
type: 'relation',
|
type: 'relation',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'accountOwner',
|
fieldName: 'accountOwner',
|
||||||
@@ -43,7 +43,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'employees',
|
key: 'employees',
|
||||||
name: 'Employees',
|
name: 'Employees',
|
||||||
icon: <IconUsers />,
|
Icon: IconUsers,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'employees',
|
fieldName: 'employees',
|
||||||
@@ -53,7 +53,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'address',
|
key: 'address',
|
||||||
name: 'Address',
|
name: 'Address',
|
||||||
icon: <IconMap />,
|
Icon: IconMap,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'address',
|
fieldName: 'address',
|
||||||
@@ -63,7 +63,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'idealCustomerProfile',
|
key: 'idealCustomerProfile',
|
||||||
name: 'ICP',
|
name: 'ICP',
|
||||||
icon: <IconTarget />,
|
Icon: IconTarget,
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'idealCustomerProfile',
|
fieldName: 'idealCustomerProfile',
|
||||||
@@ -72,7 +72,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'xUrl',
|
key: 'xUrl',
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
icon: <IconBrandX />,
|
Icon: IconBrandX,
|
||||||
type: 'url',
|
type: 'url',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'xUrl',
|
fieldName: 'xUrl',
|
||||||
@@ -82,7 +82,7 @@ export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
name: 'Created at',
|
name: 'Created at',
|
||||||
icon: <IconCalendar />,
|
Icon: IconCalendar,
|
||||||
type: 'date',
|
type: 'date',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'createdAt',
|
fieldName: 'createdAt',
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconUser,
|
IconUser,
|
||||||
} from '@/ui/icon/index';
|
} from '@/ui/icon/index';
|
||||||
import { icon } from '@/ui/theme/constants/icon';
|
|
||||||
import { PipelineProgress } from '~/generated/graphql';
|
import { PipelineProgress } from '~/generated/graphql';
|
||||||
|
|
||||||
import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect';
|
import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect';
|
||||||
@@ -17,21 +16,19 @@ export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[]
|
|||||||
{
|
{
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
icon: <IconCurrencyDollar size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconCurrencyDollar,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'closeDate',
|
key: 'closeDate',
|
||||||
label: 'Close date',
|
label: 'Close date',
|
||||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconCalendarEvent,
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'companyId',
|
key: 'companyId',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: (
|
Icon: IconBuildingSkyscraper,
|
||||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
|
||||||
),
|
|
||||||
type: 'entity',
|
type: 'entity',
|
||||||
entitySelectComponent: (
|
entitySelectComponent: (
|
||||||
<FilterDropdownCompanySearchSelect
|
<FilterDropdownCompanySearchSelect
|
||||||
@@ -42,7 +39,7 @@ export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[]
|
|||||||
{
|
{
|
||||||
key: 'pointOfContactId',
|
key: 'pointOfContactId',
|
||||||
label: 'Point of contact',
|
label: 'Point of contact',
|
||||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconUser,
|
||||||
type: 'entity',
|
type: 'entity',
|
||||||
entitySelectComponent: (
|
entitySelectComponent: (
|
||||||
<FilterDropdownPeopleSearchSelect
|
<FilterDropdownPeopleSearchSelect
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ export const opportunitiesSorts = [
|
|||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
label: 'Creation',
|
label: 'Creation',
|
||||||
icon: <IconCalendarEvent size={16} />,
|
Icon: IconCalendarEvent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
icon: <IconCurrencyDollar size={16} />,
|
Icon: IconCurrencyDollar,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'closeDate',
|
key: 'closeDate',
|
||||||
label: 'Expected close date',
|
label: 'Expected close date',
|
||||||
icon: <IconCalendarEvent size={16} />,
|
Icon: IconCalendarEvent,
|
||||||
},
|
},
|
||||||
] satisfies Array<SortType<PipelineProgresses_Order_By>>;
|
] satisfies Array<SortType<PipelineProgresses_Order_By>>;
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ export const Email: Story = {
|
|||||||
const filterButton = await canvas.findByText('Filter');
|
const filterButton = await canvas.findByText('Filter');
|
||||||
await userEvent.click(filterButton);
|
await userEvent.click(filterButton);
|
||||||
|
|
||||||
const emailFilterButton = (
|
const emailFilterButton = await canvas.findByTestId('select-filter-2');
|
||||||
await canvas.findAllByTestId('dropdown-menu-item')
|
|
||||||
).find((item) => {
|
|
||||||
return item.textContent?.includes('Email');
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(emailFilterButton);
|
assert(emailFilterButton);
|
||||||
|
|
||||||
@@ -70,11 +66,7 @@ export const CompanyName: Story = {
|
|||||||
const filterButton = await canvas.findByText('Filter');
|
const filterButton = await canvas.findByText('Filter');
|
||||||
await userEvent.click(filterButton);
|
await userEvent.click(filterButton);
|
||||||
|
|
||||||
const companyFilterButton = (
|
const companyFilterButton = await canvas.findByTestId('select-filter-3');
|
||||||
await canvas.findAllByTestId('dropdown-menu-item')
|
|
||||||
).find((item) => {
|
|
||||||
return item.textContent?.includes('Company');
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(companyFilterButton);
|
assert(companyFilterButton);
|
||||||
|
|
||||||
@@ -87,7 +79,7 @@ export const CompanyName: Story = {
|
|||||||
|
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
|
|
||||||
const qontoChip = (await canvas.findAllByTestId('dropdown-menu-item')).find(
|
const qontoChip = (await canvas.findAllByTestId('menu-item')).find(
|
||||||
(item) => {
|
(item) => {
|
||||||
return item.textContent?.includes('Qonto');
|
return item.textContent?.includes('Qonto');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ export const Email: Story = {
|
|||||||
const sortButton = await canvas.findByText('Sort');
|
const sortButton = await canvas.findByText('Sort');
|
||||||
await userEvent.click(sortButton);
|
await userEvent.click(sortButton);
|
||||||
|
|
||||||
const emailSortButton = canvas.getByText('Email', {
|
const emailSortButton = await canvas.findByTestId('select-sort-2');
|
||||||
selector: 'li > div > div',
|
|
||||||
});
|
|
||||||
await userEvent.click(emailSortButton);
|
await userEvent.click(emailSortButton);
|
||||||
|
|
||||||
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
|
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
|
||||||
@@ -52,9 +50,7 @@ export const Cancel: Story = {
|
|||||||
const sortButton = await canvas.findByText('Sort');
|
const sortButton = await canvas.findByText('Sort');
|
||||||
await userEvent.click(sortButton);
|
await userEvent.click(sortButton);
|
||||||
|
|
||||||
const emailSortButton = canvas.getByText('Email', {
|
const emailSortButton = await canvas.findByTestId('select-sort-2');
|
||||||
selector: 'li > div > div',
|
|
||||||
});
|
|
||||||
await userEvent.click(emailSortButton);
|
await userEvent.click(emailSortButton);
|
||||||
|
|
||||||
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
|
expect(await canvas.getByTestId('remove-icon-email')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
icon: <IconMail />,
|
Icon: IconMail,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'email',
|
fieldName: 'email',
|
||||||
@@ -33,7 +33,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'company',
|
key: 'company',
|
||||||
name: 'Company',
|
name: 'Company',
|
||||||
icon: <IconBuildingSkyscraper />,
|
Icon: IconBuildingSkyscraper,
|
||||||
type: 'relation',
|
type: 'relation',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'company',
|
fieldName: 'company',
|
||||||
@@ -44,7 +44,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
name: 'Phone',
|
name: 'Phone',
|
||||||
icon: <IconPhone />,
|
Icon: IconPhone,
|
||||||
type: 'phone',
|
type: 'phone',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'phone',
|
fieldName: 'phone',
|
||||||
@@ -54,7 +54,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'jobTitle',
|
key: 'jobTitle',
|
||||||
name: 'Job Title',
|
name: 'Job Title',
|
||||||
icon: <IconBriefcase />,
|
Icon: IconBriefcase,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'jobTitle',
|
fieldName: 'jobTitle',
|
||||||
@@ -64,7 +64,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'city',
|
key: 'city',
|
||||||
name: 'City',
|
name: 'City',
|
||||||
icon: <IconMap />,
|
Icon: IconMap,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'city',
|
fieldName: 'city',
|
||||||
@@ -74,7 +74,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'linkedinUrl',
|
key: 'linkedinUrl',
|
||||||
name: 'Linkedin URL',
|
name: 'Linkedin URL',
|
||||||
icon: <IconBrandLinkedin />,
|
Icon: IconBrandLinkedin,
|
||||||
type: 'url',
|
type: 'url',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'linkedinUrl',
|
fieldName: 'linkedinUrl',
|
||||||
@@ -84,7 +84,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'xUrl',
|
key: 'xUrl',
|
||||||
name: 'X URL',
|
name: 'X URL',
|
||||||
icon: <IconBrandX />,
|
Icon: IconBrandX,
|
||||||
type: 'url',
|
type: 'url',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'xUrl',
|
fieldName: 'xUrl',
|
||||||
@@ -94,7 +94,7 @@ export const personShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
|
|||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
name: 'Created at',
|
name: 'Created at',
|
||||||
icon: <IconCalendar />,
|
Icon: IconCalendar,
|
||||||
type: 'date',
|
type: 'date',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'createdAt',
|
fieldName: 'createdAt',
|
||||||
|
|||||||
@@ -9,34 +9,31 @@ import {
|
|||||||
IconUser,
|
IconUser,
|
||||||
} from '@/ui/icon/index';
|
} from '@/ui/icon/index';
|
||||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||||
import { icon } from '@/ui/theme/constants/icon';
|
|
||||||
import { Person } from '~/generated/graphql';
|
import { Person } from '~/generated/graphql';
|
||||||
|
|
||||||
export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
|
export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
|
||||||
{
|
{
|
||||||
key: 'firstName',
|
key: 'firstName',
|
||||||
label: 'First name',
|
label: 'First name',
|
||||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconUser,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'lastName',
|
key: 'lastName',
|
||||||
label: 'Last name',
|
label: 'Last name',
|
||||||
icon: <IconUser size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconUser,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
icon: <IconMail size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconMail,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'companyId',
|
key: 'companyId',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: (
|
Icon: IconBuildingSkyscraper,
|
||||||
<IconBuildingSkyscraper size={icon.size.md} stroke={icon.stroke.sm} />
|
|
||||||
),
|
|
||||||
type: 'entity',
|
type: 'entity',
|
||||||
entitySelectComponent: (
|
entitySelectComponent: (
|
||||||
<FilterDropdownCompanySearchSelect context={TableRecoilScopeContext} />
|
<FilterDropdownCompanySearchSelect context={TableRecoilScopeContext} />
|
||||||
@@ -45,19 +42,19 @@ export const peopleFilters: FilterDefinitionByEntity<Person>[] = [
|
|||||||
{
|
{
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
icon: <IconPhone size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconPhone,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <IconCalendarEvent size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconCalendarEvent,
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'city',
|
key: 'city',
|
||||||
label: 'City',
|
label: 'City',
|
||||||
icon: <IconMap size={icon.size.md} stroke={icon.stroke.sm} />,
|
Icon: IconMap,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const availableSorts: SortType<People_Order_By>[] = [
|
|||||||
{
|
{
|
||||||
key: 'fullname',
|
key: 'fullname',
|
||||||
label: 'People',
|
label: 'People',
|
||||||
icon: <IconUser size={16} />,
|
Icon: IconUser,
|
||||||
|
|
||||||
orderByTemplate: (order: Order_By) => [
|
orderByTemplate: (order: Order_By) => [
|
||||||
{ firstName: order },
|
{ firstName: order },
|
||||||
@@ -26,28 +26,27 @@ export const availableSorts: SortType<People_Order_By>[] = [
|
|||||||
{
|
{
|
||||||
key: 'company_name',
|
key: 'company_name',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: <IconBuildingSkyscraper size={16} />,
|
Icon: IconBuildingSkyscraper,
|
||||||
|
|
||||||
orderByTemplate: (order: Order_By) => [{ company: { name: order } }],
|
orderByTemplate: (order: Order_By) => [{ company: { name: order } }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
icon: <IconMail size={16} />,
|
Icon: IconMail,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'phone',
|
key: 'phone',
|
||||||
label: 'Phone',
|
label: 'Phone',
|
||||||
icon: <IconPhone size={16} />,
|
Icon: IconPhone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'createdAt',
|
key: 'createdAt',
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
icon: <IconCalendarEvent size={16} />,
|
Icon: IconCalendarEvent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'city',
|
key: 'city',
|
||||||
label: 'City',
|
label: 'City',
|
||||||
icon: <IconMap size={16} />,
|
Icon: IconMap,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const tasksFilters: FilterDefinitionByEntity<Activity>[] = [
|
|||||||
{
|
{
|
||||||
key: 'assigneeId',
|
key: 'assigneeId',
|
||||||
label: 'Assignee',
|
label: 'Assignee',
|
||||||
icon: <IconUser />,
|
Icon: IconUser,
|
||||||
type: 'entity',
|
type: 'entity',
|
||||||
entitySelectComponent: (
|
entitySelectComponent: (
|
||||||
<FilterDropdownUserSearchSelect context={TasksRecoilScopeContext} />
|
<FilterDropdownUserSearchSelect context={TasksRecoilScopeContext} />
|
||||||
|
|||||||
Reference in New Issue
Block a user