diff --git a/packages/twenty-front/.eslintrc.cjs b/packages/twenty-front/.eslintrc.cjs index b9f9d2051..a3d0978eb 100644 --- a/packages/twenty-front/.eslintrc.cjs +++ b/packages/twenty-front/.eslintrc.cjs @@ -106,6 +106,7 @@ module.exports = { '*.config.ts', '*config.js', 'codegen*', + 'tsup.ui.index.tsx' ], overrides: [ { diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 7eba2245e..6af91f50e 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -9,7 +9,6 @@ "build": "tsc && vite build && yarn build:inject-runtime-env", "build:inject-runtime-env": "sh ./scripts/inject-runtime-env.sh", "preview": "vite preview", - "eslint-plugin:setup": "cd ../packages/eslint-plugin-twenty/ && yarn && yarn build && cd ../../front/ && yarn upgrade eslint-plugin-twenty", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "fmt:fix": "prettier --cache --write \"src/**/*.ts\" \"src/**/*.tsx\"", "fmt": "prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"", @@ -66,6 +65,7 @@ "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", "luxon": "^3.3.0", + "nx": "17.2.3", "react": "^18.2.0", "react-data-grid": "7.0.0-beta.13", "react-datepicker": "^4.11.0", diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleEntitySelect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleEntitySelect.tsx index e48af44fc..bb7aff7a1 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleEntitySelect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/MultipleEntitySelect.tsx @@ -1,11 +1,16 @@ +/* eslint-disable react-hooks/rules-of-hooks */ import { useRef } from 'react'; import { isNonEmptyString } from '@sniptt/guards'; import debounce from 'lodash.debounce'; +import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; @@ -71,6 +76,8 @@ export const MultipleEntitySelect = < }, }); + const selectableItemIds = entitiesInDropdown.map((entity) => entity.id); + return ( - {entitiesInDropdown?.map((entity) => ( - - onChange({ ...value, [entity.id]: newCheckedValue }) + { + if (_itemId in value === false || value[_itemId] === false) { + onChange({ ...value, [_itemId]: true }); + } else { + onChange({ ...value, [_itemId]: false }); } - avatar={ - + {entitiesInDropdown?.map((entity) => ( + + + onChange({ ...value, [entity.id]: newCheckedValue }) + } + avatar={ + + } + text={entity.name} /> - } - text={entity.name} - /> - ))} + + ))} + {entitiesInDropdown?.length === 0 && } diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx index 2026735ca..440402d9e 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/SingleEntitySelectBase.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/rules-of-hooks */ import { useRef } from 'react'; import { isNonEmptyString } from '@sniptt/guards'; import { Key } from 'ts-key-enum'; @@ -6,6 +7,9 @@ import { IconPlus } from '@/ui/display/icon'; import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; +import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; +import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect'; import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar'; @@ -64,7 +68,7 @@ export const SingleEntitySelectBase = < assertNotNull(entity) && isNonEmptyString(entity.name), ); - const { preselectedOptionId, resetScroll } = useEntitySelectScroll({ + const { preselectedOptionId } = useEntitySelectScroll({ selectableOptionIds: [ EmptyButtonId, ...entitiesInDropdown.map((item) => item.id), @@ -73,24 +77,6 @@ export const SingleEntitySelectBase = < containerRef, }); - useScopedHotkeys( - Key.Enter, - () => { - if (showCreateButton && preselectedOptionId === CreateButtonId) { - onCreate?.(); - } else { - const entity = entitiesInDropdown.findIndex( - (entity) => entity.id === preselectedOptionId, - ); - onEntitySelected(entitiesInDropdown[entity]); - } - - resetScroll(); - }, - RelationPickerHotkeyScope.RelationPicker, - [entitiesInDropdown, preselectedOptionId, onEntitySelected], - ); - useScopedHotkeys( Key.Escape, () => { @@ -100,6 +86,8 @@ export const SingleEntitySelectBase = < [onCancel], ); + const selectableItemIds = entitiesInDropdown.map((entity) => entity.id); + return (
@@ -130,23 +118,49 @@ export const SingleEntitySelectBase = < /> )} {entitiesInDropdown?.map((entity) => ( - onEntitySelected(entity)} - text={entity.name} - hovered={preselectedOptionId === entity.id} - avatar={ - { + if ( + showCreateButton && + preselectedOptionId === CreateButtonId + ) { + onCreate?.(); + } else { + const entity = entitiesInDropdown.findIndex( + (entity) => entity.id === _itemId, + ); + onEntitySelected(entitiesInDropdown[entity]); + } + }} + > + + onEntitySelected(entity)} + text={entity.name} + selected={selectedEntity?.id === entity.id} + hovered={ + useSelectableList({ + selectableListId: 'single-entity-select-base-list', + itemId: entity.id, + }).isSelectedItemId + } + avatar={ + + } /> - } - /> + + ))} )} diff --git a/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts b/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts index 2d0495b7b..6bce33af2 100644 --- a/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts +++ b/packages/twenty-front/src/modules/object-record/utils/turnFiltersIntoWhereClause.ts @@ -115,7 +115,6 @@ export const turnFiltersIntoObjectRecordFilters = ( ); } - // eslint-disable-next-line no-case-declarations const parsedRecordIds = JSON.parse(rawUIFilter.value) as string[]; if (parsedRecordIds.length > 0) { diff --git a/packages/twenty-front/src/modules/ui/layout/card/components/__stories__/Card.stories.tsx b/packages/twenty-front/src/modules/ui/layout/card/components/__stories__/Card.stories.tsx index e47537344..1c05901a2 100644 --- a/packages/twenty-front/src/modules/ui/layout/card/components/__stories__/Card.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/card/components/__stories__/Card.stories.tsx @@ -1,4 +1,4 @@ -import { Meta, Story, StoryObj } from '@storybook/react'; +import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; diff --git a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx index a8fb33856..0f75dae3d 100644 --- a/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx +++ b/packages/twenty-front/src/modules/ui/layout/selectable-list/hooks/internal/useSelectableListHotKeys.tsx @@ -16,8 +16,7 @@ export const useSelectableListHotKeys = ( selectedItemId?: string | null, ) => { if (!selectedItemId) { - // If nothing is selected, return the default position - return { row: 0, col: 0 }; + return { row: 0, col: -1 }; } for (let row = 0; row < selectableItemIds.length; row++) { @@ -94,21 +93,23 @@ export const useSelectableListHotKeys = ( const nextId = computeNextId(direction); - if (nextId) { - const { isSelectedItemIdSelector } = getSelectableListScopedStates({ - selectableListScopeId: scopeId, - itemId: nextId, - }); - set(isSelectedItemIdSelector, true); - set(selectedItemIdState, nextId); - } + if (!selectedItemId || (selectedItemId && selectedItemId !== nextId)) { + if (nextId) { + const { isSelectedItemIdSelector } = getSelectableListScopedStates({ + selectableListScopeId: scopeId, + itemId: nextId, + }); + set(isSelectedItemIdSelector, true); + set(selectedItemIdState, nextId); + } - if (selectedItemId) { - const { isSelectedItemIdSelector } = getSelectableListScopedStates({ - selectableListScopeId: scopeId, - itemId: selectedItemId, - }); - set(isSelectedItemIdSelector, false); + if (selectedItemId) { + const { isSelectedItemIdSelector } = getSelectableListScopedStates({ + selectableListScopeId: scopeId, + itemId: selectedItemId, + }); + set(isSelectedItemIdSelector, false); + } } }, [scopeId], diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx index 1521d5fb0..796869994 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar.tsx @@ -19,6 +19,7 @@ const StyledLeftContentWithCheckboxContainer = styled.div` type MenuItemMultiSelectAvatarProps = { avatar?: ReactNode; selected: boolean; + isKeySelected: boolean; text: string; className?: string; onSelectChange?: (selected: boolean) => void; @@ -29,6 +30,7 @@ export const MenuItemMultiSelectAvatar = ({ text, selected, className, + isKeySelected, onSelectChange, }: MenuItemMultiSelectAvatarProps) => { const handleOnClick = () => { @@ -36,7 +38,11 @@ export const MenuItemMultiSelectAvatar = ({ }; return ( - + diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx index 2f5ffeb51..c67a1c574 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/internals/components/StyledMenuItemBase.tsx @@ -7,6 +7,7 @@ import { MenuItemAccent } from '../../types/MenuItemAccent'; export type MenuItemBaseProps = { accent?: MenuItemAccent; + isKeySelected?: boolean; }; export const StyledMenuItemBase = styled.li` @@ -15,8 +16,13 @@ export const StyledMenuItemBase = styled.li` align-items: center; + background: ${({ isKeySelected, theme }) => + isKeySelected + ? theme.background.transparent.light + : theme.background.primary}; border-radius: ${({ theme }) => theme.border.radius.sm}; cursor: pointer; + display: flex; flex-direction: row; @@ -26,8 +32,8 @@ export const StyledMenuItemBase = styled.li` gap: ${({ theme }) => theme.spacing(2)}; height: calc(32px - 2 * var(--vertical-padding)); - justify-content: space-between; + padding: var(--vertical-padding) var(--horizontal-padding); ${hoverBackground}; diff --git a/yarn.lock b/yarn.lock index 10216563b..a56a596b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7235,6 +7235,18 @@ __metadata: languageName: node linkType: hard +"@nrwl/tao@npm:17.2.3": + version: 17.2.3 + resolution: "@nrwl/tao@npm:17.2.3" + dependencies: + nx: "npm:17.2.3" + tslib: "npm:^2.3.0" + bin: + tao: index.js + checksum: db353ea11664e9db2f3e3e5956748452cd50ca01b400170aaa667f1d1853175731c5e6377f756f58b87101c9cc04e210447cec3b64e16a25b27882d4eea17e0e + languageName: node + linkType: hard + "@nuxtjs/opencollective@npm:0.3.2": version: 0.3.2 resolution: "@nuxtjs/opencollective@npm:0.3.2" @@ -7255,6 +7267,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-arm64@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-darwin-arm64@npm:17.2.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-darwin-x64@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-darwin-x64@npm:17.2.0" @@ -7262,6 +7281,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-darwin-x64@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-darwin-x64@npm:17.2.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-freebsd-x64@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-freebsd-x64@npm:17.2.0" @@ -7269,6 +7295,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-freebsd-x64@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-freebsd-x64@npm:17.2.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@nx/nx-linux-arm-gnueabihf@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-linux-arm-gnueabihf@npm:17.2.0" @@ -7276,6 +7309,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm-gnueabihf@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-linux-arm-gnueabihf@npm:17.2.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@nx/nx-linux-arm64-gnu@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-linux-arm64-gnu@npm:17.2.0" @@ -7283,6 +7323,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-gnu@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-linux-arm64-gnu@npm:17.2.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-arm64-musl@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-linux-arm64-musl@npm:17.2.0" @@ -7290,6 +7337,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-arm64-musl@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-linux-arm64-musl@npm:17.2.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-linux-x64-gnu@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-linux-x64-gnu@npm:17.2.0" @@ -7297,6 +7351,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-gnu@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-linux-x64-gnu@npm:17.2.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@nx/nx-linux-x64-musl@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-linux-x64-musl@npm:17.2.0" @@ -7304,6 +7365,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-linux-x64-musl@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-linux-x64-musl@npm:17.2.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@nx/nx-win32-arm64-msvc@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-win32-arm64-msvc@npm:17.2.0" @@ -7311,6 +7379,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-arm64-msvc@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-win32-arm64-msvc@npm:17.2.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@nx/nx-win32-x64-msvc@npm:17.2.0": version: 17.2.0 resolution: "@nx/nx-win32-x64-msvc@npm:17.2.0" @@ -7318,6 +7393,13 @@ __metadata: languageName: node linkType: hard +"@nx/nx-win32-x64-msvc@npm:17.2.3": + version: 17.2.3 + resolution: "@nx/nx-win32-x64-msvc@npm:17.2.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@open-draft/deferred-promise@npm:^2.1.0, @open-draft/deferred-promise@npm:^2.2.0": version: 2.2.0 resolution: "@open-draft/deferred-promise@npm:2.2.0" @@ -19087,8 +19169,8 @@ __metadata: "eslint-plugin-twenty@file:../eslint-plugin-twenty::locator=twenty-front%40workspace%3Apackages%2Ftwenty-front": version: 1.0.3 - resolution: "eslint-plugin-twenty@file:../eslint-plugin-twenty#../eslint-plugin-twenty::hash=2e5a59&locator=twenty-front%40workspace%3Apackages%2Ftwenty-front" - checksum: 4b12b2be1d0f355f9b932f209abbda06fe51eca3092592ce443ddfb379dc12af0d00e5a124acc0070b3913b0c6468ba7fb513cd13251c4684abaf2b00152bdea + resolution: "eslint-plugin-twenty@file:../eslint-plugin-twenty#../eslint-plugin-twenty::hash=1e4d37&locator=twenty-front%40workspace%3Apackages%2Ftwenty-front" + checksum: 8d9085c852371f3cd641baca9865f85f3dbea8e4886fee4ab62ff5d6e040c7674b6997fe352c6416fecbd5ef426d912932f27c0c1e680c021e61c9497b2a767e languageName: node linkType: hard @@ -27824,6 +27906,90 @@ __metadata: languageName: node linkType: hard +"nx@npm:17.2.3": + version: 17.2.3 + resolution: "nx@npm:17.2.3" + dependencies: + "@nrwl/tao": "npm:17.2.3" + "@nx/nx-darwin-arm64": "npm:17.2.3" + "@nx/nx-darwin-x64": "npm:17.2.3" + "@nx/nx-freebsd-x64": "npm:17.2.3" + "@nx/nx-linux-arm-gnueabihf": "npm:17.2.3" + "@nx/nx-linux-arm64-gnu": "npm:17.2.3" + "@nx/nx-linux-arm64-musl": "npm:17.2.3" + "@nx/nx-linux-x64-gnu": "npm:17.2.3" + "@nx/nx-linux-x64-musl": "npm:17.2.3" + "@nx/nx-win32-arm64-msvc": "npm:17.2.3" + "@nx/nx-win32-x64-msvc": "npm:17.2.3" + "@yarnpkg/lockfile": "npm:^1.1.0" + "@yarnpkg/parsers": "npm:3.0.0-rc.46" + "@zkochan/js-yaml": "npm:0.0.6" + axios: "npm:^1.5.1" + chalk: "npm:^4.1.0" + cli-cursor: "npm:3.1.0" + cli-spinners: "npm:2.6.1" + cliui: "npm:^8.0.1" + dotenv: "npm:~16.3.1" + dotenv-expand: "npm:~10.0.0" + enquirer: "npm:~2.3.6" + figures: "npm:3.2.0" + flat: "npm:^5.0.2" + fs-extra: "npm:^11.1.0" + glob: "npm:7.1.4" + ignore: "npm:^5.0.4" + jest-diff: "npm:^29.4.1" + js-yaml: "npm:4.1.0" + jsonc-parser: "npm:3.2.0" + lines-and-columns: "npm:~2.0.3" + minimatch: "npm:3.0.5" + node-machine-id: "npm:1.1.12" + npm-run-path: "npm:^4.0.1" + open: "npm:^8.4.0" + semver: "npm:7.5.3" + string-width: "npm:^4.2.3" + strong-log-transformer: "npm:^2.1.0" + tar-stream: "npm:~2.2.0" + tmp: "npm:~0.2.1" + tsconfig-paths: "npm:^4.1.2" + tslib: "npm:^2.3.0" + yargs: "npm:^17.6.2" + yargs-parser: "npm:21.1.1" + peerDependencies: + "@swc-node/register": ^1.6.7 + "@swc/core": ^1.3.85 + dependenciesMeta: + "@nx/nx-darwin-arm64": + optional: true + "@nx/nx-darwin-x64": + optional: true + "@nx/nx-freebsd-x64": + optional: true + "@nx/nx-linux-arm-gnueabihf": + optional: true + "@nx/nx-linux-arm64-gnu": + optional: true + "@nx/nx-linux-arm64-musl": + optional: true + "@nx/nx-linux-x64-gnu": + optional: true + "@nx/nx-linux-x64-musl": + optional: true + "@nx/nx-win32-arm64-msvc": + optional: true + "@nx/nx-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc-node/register": + optional: true + "@swc/core": + optional: true + bin: + nx: bin/nx.js + nx-cloud: bin/nx-cloud.js + checksum: 56c343fa73dd58b37ca517b8fadfa9cbe1ff0ad59edf7b51f85a3bb81ed8da58b92c679d66884a2146d838d566b122468a94902dc86292b2902bf442f77d43b2 + languageName: node + linkType: hard + "nyc@npm:^15.1.0": version: 15.1.0 resolution: "nyc@npm:15.1.0" @@ -34371,6 +34537,7 @@ __metadata: luxon: "npm:^3.3.0" msw: "npm:^2.0.11" msw-storybook-addon: "npm:2.0.0--canary.122.b3ed3b1.0" + nx: "npm:17.2.3" prettier: "npm:^3.1.0" react: "npm:^18.2.0" react-data-grid: "npm:7.0.0-beta.13"