Migrate from ESLint to OxLint (#18443)
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { type FieldMultiSelectValue } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
@@ -27,7 +28,7 @@ export const MultiSelectDisplay = ({
|
||||
? options?.filter((option) => values.includes(option.value))
|
||||
: [];
|
||||
|
||||
if (!selectedOptions) return null;
|
||||
if (!isDefined(selectedOptions)) return null;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
|
||||
@@ -122,7 +123,7 @@ export const CurrencyInput = ({
|
||||
onChange={handleCurrencyChange}
|
||||
/>
|
||||
<StyledIcon>
|
||||
{Icon && (
|
||||
{isDefined(Icon) && (
|
||||
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
)}
|
||||
</StyledIcon>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
// eslint-disable-next-line twenty/styled-components-prefixed-with-styled
|
||||
// oxlint-disable-next-line twenty/styled-components-prefixed-with-styled
|
||||
export const FieldInputContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { css } from '@linaria/core';
|
||||
import { styled } from '@linaria/react';
|
||||
import React, { type ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
@@ -212,7 +213,7 @@ export const IconPicker = ({
|
||||
const icons = getIcons();
|
||||
|
||||
const totalMatchingIconsCount = useMemo(() => {
|
||||
if (!icons) return 0;
|
||||
if (!isDefined(icons)) return 0;
|
||||
|
||||
return Object.keys(icons).filter((iconKey) => {
|
||||
const iconLabel = convertIconKeyToLabel(iconKey)
|
||||
@@ -265,7 +266,8 @@ export const IconPicker = ({
|
||||
.map(({ iconKey }) => iconKey);
|
||||
|
||||
const isSelectedIconMatchingFilter =
|
||||
selectedIconKey && filteredAndSortedIconKeys.includes(selectedIconKey);
|
||||
isDefined(selectedIconKey) &&
|
||||
filteredAndSortedIconKeys.includes(selectedIconKey);
|
||||
|
||||
return isSelectedIconMatchingFilter
|
||||
? [
|
||||
@@ -306,7 +308,7 @@ export const IconPicker = ({
|
||||
dropdownId={dropdownId}
|
||||
dropdownOffset={dropdownOffset}
|
||||
clickableComponent={
|
||||
clickableComponent || (
|
||||
clickableComponent ?? (
|
||||
<IconButton
|
||||
ariaLabel={t`Click to select icon ${iconAriaLabel}`}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import React from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledInputErrorHelper = styled.div`
|
||||
@@ -15,7 +16,7 @@ export const InputErrorHelper = ({
|
||||
children?: React.ReactNode;
|
||||
}) => (
|
||||
<div>
|
||||
{children && (
|
||||
{isDefined(children) && (
|
||||
<StyledInputErrorHelper aria-live="polite">
|
||||
{children}
|
||||
</StyledInputErrorHelper>
|
||||
|
||||
@@ -64,7 +64,7 @@ export const SelectInput = ({
|
||||
option.value !== selectedOption?.value &&
|
||||
normalizeSearchText(option.label).includes(searchTerm)
|
||||
);
|
||||
}) || []
|
||||
})
|
||||
);
|
||||
}, [options, searchFilter, selectedOption?.value]);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ export const SettingsTextInput = ({
|
||||
return (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
dataTestId={dataTestId}
|
||||
onFocus={handleFocus}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { InputErrorHelper } from '@/ui/input/components/InputErrorHelper';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { css } from '@linaria/core';
|
||||
import { styled } from '@linaria/react';
|
||||
@@ -143,7 +144,9 @@ const StyledInput = styled.input<
|
||||
? `calc(${themeCssVariables.spacing[3]} + 16px)`
|
||||
: themeCssVariables.spacing[2]};
|
||||
width: ${({ width }) =>
|
||||
width ? `calc(${width}px + ${themeCssVariables.spacing[0.5]})` : '100%'};
|
||||
isDefined(width)
|
||||
? `calc(${width}px + ${themeCssVariables.spacing[0.5]})`
|
||||
: '100%'};
|
||||
max-width: ${({ autoGrow }) => (autoGrow ? '100%' : 'none')};
|
||||
text-overflow: ellipsis;
|
||||
&::placeholder,
|
||||
@@ -336,7 +339,7 @@ const TextInputComponent = forwardRef<
|
||||
id={instanceId}
|
||||
width={width}
|
||||
data-testid={dataTestId}
|
||||
autoComplete={autoComplete || 'off'}
|
||||
autoComplete={autoComplete ?? 'off'}
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
onFocus={handleFocus}
|
||||
@@ -457,10 +460,10 @@ const TextInputWithAutoGrowWrapper = forwardRef<
|
||||
{props.autoGrow ? (
|
||||
<StyledAutogrowWrapper
|
||||
sizeVariant={props.sizeVariant}
|
||||
node={props.value || props.placeholder}
|
||||
node={props.value ?? props.placeholder}
|
||||
>
|
||||
<TextInputComponent
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
fullWidth={true}
|
||||
@@ -468,7 +471,7 @@ const TextInputWithAutoGrowWrapper = forwardRef<
|
||||
</StyledAutogrowWrapper>
|
||||
) : (
|
||||
<TextInputComponent
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ const IconPickerStory = (args: IconPickerStoryProps) => {
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...args}
|
||||
onChange={({ iconKey }) => {
|
||||
setSelectedIconKey(iconKey);
|
||||
@@ -33,7 +33,7 @@ const meta: Meta<typeof IconPicker> = {
|
||||
component: IconPicker,
|
||||
decorators: [IconsProviderDecorator, ComponentDecorator],
|
||||
render: (args: IconPickerProps) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
<IconPickerStory key={args.selectedIconKey ?? 'no-selection'} {...args} />
|
||||
),
|
||||
};
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ const Render = (args: RenderProps) => {
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
return <Select {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ const Render = (args: RenderProps) => {
|
||||
setValue(text);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextArea {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ const Render = (args: RenderProps) => {
|
||||
setValue(text);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
return <TextInput {...args} value={value} onChange={handleChange} />;
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import { styled } from '@linaria/react';
|
||||
import { useState } from 'react';
|
||||
import { type Nullable } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
relativeDateFilterSchema,
|
||||
type RelativeDateFilter,
|
||||
type RelativeDateFilterDirection,
|
||||
@@ -55,7 +56,7 @@ export const RelativeDatePickerHeader = ({
|
||||
|
||||
const [draftAmountValue, setDraftAmountValue] = useState(amountTextValue);
|
||||
|
||||
const isUnitPlural = amount && amount > 1 && direction !== 'THIS';
|
||||
const isUnitPlural = isDefined(amount) && amount > 1 && direction !== 'THIS';
|
||||
const unitOptionsSource = allowIntraDayUnits
|
||||
? RELATIVE_DATETIME_UNITS_SELECT_OPTIONS
|
||||
: RELATIVE_DATE_UNITS_SELECT_OPTIONS;
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { format, isValid } from 'date-fns';
|
||||
import { getDateTimeFormatStringFoDatePickerInputMask } from '~/utils/date-utils';
|
||||
|
||||
@@ -6,7 +7,7 @@ export const useParseJSDateToIMaskDateTimeInputString = () => {
|
||||
const { dateFormat, timeFormat } = useDateTimeFormat();
|
||||
|
||||
const parseJSDateToDateTimeInputString = (date: Date) => {
|
||||
if (!date || !isValid(date)) {
|
||||
if (!isDefined(date) || !isValid(date)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -45,9 +45,9 @@ export const DraggableItem = ({
|
||||
return (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.draggableProps}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...draggableProvided.dragHandleProps}
|
||||
style={{
|
||||
...draggableComponentStyles,
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export const DraggableList = ({
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
// oxlint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{draggableItems}
|
||||
|
||||
+5
-2
@@ -1,5 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ComponentProps, type MouseEvent } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledHeader = styled.li`
|
||||
@@ -64,9 +65,11 @@ export const DropdownMenuHeader = ({
|
||||
}: DropdownMenuHeaderProps) => {
|
||||
return (
|
||||
<StyledHeader data-testid={testId} className={className} onClick={onClick}>
|
||||
{StartComponent && StartComponent}
|
||||
{isDefined(StartComponent) && StartComponent}
|
||||
<StyledChildrenWrapper>{children}</StyledChildrenWrapper>
|
||||
{EndComponent && <StyledEndComponent>{EndComponent}</StyledEndComponent>}
|
||||
{isDefined(EndComponent) && (
|
||||
<StyledEndComponent>{EndComponent}</StyledEndComponent>
|
||||
)}
|
||||
</StyledHeader>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ import {
|
||||
import 'react-phone-number-input/style.css';
|
||||
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/ui/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -140,10 +141,10 @@ export const DropdownMenuInput = forwardRef<
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
ref={combinedRef}
|
||||
withRightComponent={!!rightComponent}
|
||||
withRightComponent={isDefined(rightComponent)}
|
||||
/>
|
||||
)}
|
||||
{!!rightComponent && (
|
||||
{isDefined(rightComponent) && (
|
||||
<StyledRightContainer>{rightComponent}</StyledRightContainer>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ describe('useCloseDropdown', () => {
|
||||
it('should close dropdown from inside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
@@ -87,7 +87,7 @@ describe('useCloseDropdown', () => {
|
||||
it('should close dropdown from outside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ describe('useOpenDropdown', () => {
|
||||
it('should open dropdown from inside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
@@ -75,7 +75,7 @@ describe('useOpenDropdown', () => {
|
||||
it('should open dropdown from outside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ describe('useToggleDropdown', () => {
|
||||
it('should toggle dropdown from inside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
@@ -89,7 +89,7 @@ describe('useToggleDropdown', () => {
|
||||
it('should toggle dropdown from outside component instance context', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
// eslint-disable-next-line twenty/matching-state-variable
|
||||
// oxlint-disable-next-line twenty/matching-state-variable
|
||||
const isOutsideDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
outsideDropdownId,
|
||||
|
||||
@@ -42,7 +42,7 @@ export const StyledCenteredButton = (
|
||||
props: React.ComponentProps<typeof Button>,
|
||||
) => (
|
||||
<StyledCenteredButtonContainer>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<Button {...props} />
|
||||
</StyledCenteredButtonContainer>
|
||||
);
|
||||
@@ -74,7 +74,7 @@ export const StyledConfirmationButton = (
|
||||
props: React.ComponentProps<typeof Button>,
|
||||
) => (
|
||||
<StyledConfirmationButtonContainer>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<Button {...props} />
|
||||
</StyledConfirmationButtonContainer>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
// eslint-disable-next-line twenty/styled-components-prefixed-with-styled
|
||||
// oxlint-disable-next-line twenty/styled-components-prefixed-with-styled
|
||||
export const OverlayContainer = styled.div<{
|
||||
borderRadius?: 'sm' | 'md';
|
||||
hasDangerBorder?: boolean;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
MOBILE_VIEWPORT,
|
||||
ThemeContext,
|
||||
@@ -126,7 +127,7 @@ export const PageHeader = ({
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{title && (
|
||||
{isDefined(title) && (
|
||||
<StyledTitleContainer data-testid="top-bar-title">
|
||||
{typeof title === 'string' ? (
|
||||
<OverflowingTextWithTooltip text={title} />
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import {
|
||||
Breadcrumb,
|
||||
type BreadcrumbProps,
|
||||
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type JSX, type ReactNode } from 'react';
|
||||
import { PageBody } from './PageBody';
|
||||
@@ -50,7 +51,7 @@ export const SubMenuTopBarContainer = ({
|
||||
</PageHeader>
|
||||
<PageBody>
|
||||
<InformationBannerWrapper />
|
||||
{(title || reserveTitleSpace) && (
|
||||
{(isDefined(title) || reserveTitleSpace === true) && (
|
||||
<StyledTitle reserveTitleSpace={reserveTitleSpace}>
|
||||
{title}
|
||||
</StyledTitle>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const TableSubRow = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TableRow> & { children?: ReactNode }) => (
|
||||
<StyledTableSubRowContainer>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<TableRow {...props}>{children}</TableRow>
|
||||
</StyledTableSubRowContainer>
|
||||
);
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ export const StyledIconChevronDown = ({
|
||||
...props
|
||||
}: { disabled?: boolean } & React.ComponentProps<typeof IconChevronDown>) => (
|
||||
<StyledIconChevronDownContainer disabled={disabled}>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<IconChevronDown {...props} />
|
||||
</StyledIconChevronDownContainer>
|
||||
);
|
||||
|
||||
+10
-4
@@ -351,9 +351,15 @@ export const NavigationDrawerItem = ({
|
||||
danger={danger}
|
||||
soon={soon}
|
||||
as={
|
||||
to ? (isExternalLink ? 'a' : Link) : rightOptions ? 'div' : undefined
|
||||
to
|
||||
? isExternalLink
|
||||
? 'a'
|
||||
: Link
|
||||
: isDefined(rightOptions)
|
||||
? 'div'
|
||||
: undefined
|
||||
}
|
||||
role={to ? undefined : rightOptions ? 'button' : undefined}
|
||||
role={to ? undefined : isDefined(rightOptions) ? 'button' : undefined}
|
||||
to={isExternalLink ? undefined : to}
|
||||
href={isExternalLink ? to : undefined}
|
||||
target={isExternalLink ? '_blank' : undefined}
|
||||
@@ -362,7 +368,7 @@ export const NavigationDrawerItem = ({
|
||||
indentationLevel={indentationLevel}
|
||||
isNavigationDrawerExpanded={isNavigationDrawerExpanded}
|
||||
isDragging={isDragging}
|
||||
hasRightOptions={!!rightOptions}
|
||||
hasRightOptions={isDefined(rightOptions)}
|
||||
isSelectedInEditMode={isSelectedInEditMode}
|
||||
>
|
||||
<StyledItemElementsContainer>
|
||||
@@ -444,7 +450,7 @@ export const NavigationDrawerItem = ({
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
|
||||
{rightOptions && (
|
||||
{isDefined(rightOptions) && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledRightOptionsContainer
|
||||
onClick={(e) => {
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ export const NavigationDrawerSectionTitle = ({
|
||||
<StyledLabelContainer onClick={handleTitleClick}>
|
||||
<Label>{label}</Label>
|
||||
</StyledLabelContainer>
|
||||
{rightIcon && (
|
||||
{isDefined(rightIcon) && (
|
||||
<StyledRightIcon
|
||||
isMobile={isMobile}
|
||||
$alwaysVisible={alwaysShowRightIcon}
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { type AvailableWorkspace } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -11,11 +12,11 @@ export const useFilteredAvailableWorkspaces = () => {
|
||||
) => {
|
||||
return availableWorkspaces.filter(
|
||||
(availableWorkspace) =>
|
||||
currentWorkspace?.id &&
|
||||
isDefined(currentWorkspace?.id) &&
|
||||
availableWorkspace.id !== currentWorkspace.id &&
|
||||
availableWorkspace.displayName
|
||||
?.toLowerCase()
|
||||
.includes(searchValue.toLowerCase()),
|
||||
.includes(searchValue.toLowerCase()) === true,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const StepBar = ({ activeStep, children }: StepBarProps) => {
|
||||
}
|
||||
|
||||
// If the child is not a Step, return it as-is
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// oxlint-disable-next-line @typescripttypescript/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
if (child.type?.displayName !== Step.displayName) {
|
||||
return child;
|
||||
|
||||
@@ -47,7 +47,7 @@ export const useStepBar = ({ initialStep }: StepsOptions) => {
|
||||
setStep(initialStep);
|
||||
}
|
||||
// We only want this to happen on mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,7 +10,7 @@ export const useSystemColorScheme = (): ColorScheme => {
|
||||
);
|
||||
|
||||
const [preferredColorScheme, setPreferredColorScheme] = useState<ColorScheme>(
|
||||
!window.matchMedia || !mediaQuery.matches ? 'Light' : 'Dark',
|
||||
isUndefinedOrNull(window.matchMedia) || !mediaQuery.matches ? 'Light' : 'Dark',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -50,7 +50,7 @@ export const useGlobalHotkeys = ({
|
||||
async (keyboardEvent: KeyboardEvent, hotkeysEvent: any) => {
|
||||
const pendingHotkey = store.get(pendingHotkeyState.atom);
|
||||
|
||||
if (!pendingHotkey) {
|
||||
if (!isDefined(pendingHotkey)) {
|
||||
callback(keyboardEvent, hotkeysEvent);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ export const useGlobalHotkeysCallback = (
|
||||
|
||||
return callback(keyboardEvent, hotkeysEvent);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
[...dependencyArray, store],
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export const useGlobalHotkeysSequence = (
|
||||
callback: () => {
|
||||
setPendingHotkey(firstKey);
|
||||
},
|
||||
preventDefault: !!options.preventDefault,
|
||||
preventDefault: Boolean(options.preventDefault),
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ export const useHotkeysOnFocusedElement = ({
|
||||
keyboardEvent,
|
||||
hotkeysEvent,
|
||||
callback: () => {
|
||||
if (!pendingHotkey) {
|
||||
if (!isDefined(pendingHotkey)) {
|
||||
callback(keyboardEvent, hotkeysEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ export const useHotkeysOnFocusedElementCallback = (
|
||||
|
||||
return callback(keyboardEvent, hotkeysEvent);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
[...dependencyArray, store],
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ export const useListenClickOutside = <T extends Element>({
|
||||
!isClickedOnExcluded;
|
||||
|
||||
if (CLICK_OUTSIDE_DEBUG_MODE) {
|
||||
// eslint-disable-next-line no-console
|
||||
// oxlint-disable-next-line no-console
|
||||
console.log('click outside compare ref', {
|
||||
listenerId,
|
||||
shouldTrigger,
|
||||
|
||||
Reference in New Issue
Block a user