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