chore: Add @eslint-react/eslint-plugin recommended rules (#2069)

This commit is contained in:
Drew Davis 2026-04-08 08:54:18 -04:00 committed by GitHub
parent 24767c5886
commit 74536b3af5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 157 additions and 152 deletions

View file

@ -122,6 +122,10 @@ export default [
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
...reactHooksPlugin.configs.recommended.rules,
...eslintReactPlugin.configs.recommended.rules,
// Disable rules from eslint-plugin-react-hooks that have equivalent rules in @eslint-react
...eslintReactPlugin.configs['disable-conflict-eslint-plugin-react-hooks'].rules,
...eslintReactPlugin.configs['recommended-type-checked'].rules,
'react-hooks/set-state-in-effect': 'warn',
'react-hooks/exhaustive-deps': 'error',
'react-hook-form/no-use-watch': 'error',
@ -206,6 +210,7 @@ export default [
rules: {
// Drop date rules — new Date() / Date.now() are fine in tests
'no-restricted-syntax': ['error', ...UI_SYNTAX_RESTRICTIONS],
'@eslint-react/component-hook-factories': 'off',
},
},
{

View file

@ -368,6 +368,7 @@ function InsertsTab({
}
toolbarPrefix={[
<SegmentedControl
key="inserts-by-toolbar"
size="xs"
value={insertsBy ?? 'queries'}
onChange={value => {

View file

@ -1,6 +1,7 @@
import {
FormEvent,
FormEventHandler,
Fragment,
memo,
useCallback,
useEffect,
@ -2005,7 +2006,7 @@ function DBSearchPage() {
</Text>
<Grid>
{whereSuggestions!.map(s => (
<>
<Fragment key={s.corrected()}>
<Grid.Col span={10}>
<Text>{s.userMessage('where')}</Text>
</Grid.Col>
@ -2018,7 +2019,7 @@ function DBSearchPage() {
Accept
</Button>
</Grid.Col>
</>
</Fragment>
))}
</Grid>
</Box>

View file

@ -309,6 +309,20 @@ const LegendRenderer = memo<{
export const HARD_LINES_LIMIT = 60;
const StackedBarWithOverlap = (props: BarProps) => {
const { x, y, width, height, fill } = props;
// Add a tiny bit to the height to create overlap. Otherwise there's a gap
return (
<rect
x={x}
y={y}
width={width}
height={height && height > 0 ? height + 0.5 : 0}
fill={fill}
/>
);
};
export const MemoChart = memo(function MemoChart({
graphResults,
setIsClickActive,
@ -375,20 +389,6 @@ export const MemoChart = memo(function MemoChart({
const strokeDasharray = lineData[lineDataIndex]?.isDashed ? '4 3' : '0';
const seriesName = lineData[lineDataIndex]?.displayName ?? key;
const StackedBarWithOverlap = (props: BarProps) => {
const { x, y, width, height, fill } = props;
// Add a tiny bit to the height to create overlap. Otherwise there's a gap
return (
<rect
x={x}
y={y}
width={width}
height={height && height > 0 ? height + 0.5 : 0}
fill={fill}
/>
);
};
return displayType === 'stacked_bar' ? (
<Bar
key={key}

View file

@ -58,7 +58,7 @@ export const SectionWrapper: React.FC<
React.PropsWithChildren<{ title?: React.ReactNode }>
> = ({ children, title }) => (
<div className={styles.panelSectionWrapper}>
{title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
{!!title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
{children}
</div>
);

View file

@ -43,7 +43,7 @@ const SettingContainer = ({
<Group align="center" justify="space-between">
<div style={{ flex: 1 }}>
{label}
{description && (
{!!description && (
<Text size="xs" mt={2}>
{description}
</Text>

View file

@ -1,3 +1,4 @@
/* eslint-disable @eslint-react/no-create-ref */
import * as React from 'react';
import { useImperativeHandle } from 'react';
import { useRouter } from 'next/router';

View file

@ -478,7 +478,7 @@ export default function DBDeltaChart({
{/* Legend */}
<Flex gap="md" align="center" mt={2} mb="xs" wrap="wrap">
{legendPrefix}
{legendPrefix && (
{!!legendPrefix && (
<Box
h={12}
style={{

View file

@ -1329,6 +1329,11 @@ const DBSearchPageFiltersComponent = ({
);
}, [filterState, source, parentSpanIdExpr]);
const { grouped, nonGrouped } = useMemo(
() => groupFacetsByBaseName(shownFacets),
[shownFacets],
);
return (
<Box className={classes.filtersPanel} style={{ width: `${size}%` }}>
<div className={resizeStyles.resizeHandle} onMouseDown={startResize} />
@ -1480,135 +1485,127 @@ const DBSearchPageFiltersComponent = ({
)
)}
{/* Show facets even when loading to ensure pinned filters are visible while loading */}
{(() => {
const { grouped, nonGrouped } = groupFacetsByBaseName(shownFacets);
<>
{/* Render grouped facets as nested filter groups */}
{grouped.map(group => (
<NestedFilterGroup
key={group.key}
data-testid={`nested-filter-group-${group.key}`}
name={group.key}
childFilters={group.children}
selectedValues={group.children.reduce(
(acc, child) => {
acc[child.key] = filterState[child.key]
? filterState[child.key]
: { included: new Set(), excluded: new Set() };
return acc;
},
{} as Record<
string,
{
included: Set<string | boolean>;
excluded: Set<string | boolean>;
}
>,
)}
onChange={(key, value) => {
setFilterValue(key, value);
}}
onClearClick={key => clearFilter(key)}
onOnlyClick={(key, value) => {
setFilterValue(key, value, 'only');
}}
onExcludeClick={(key, value) => {
setFilterValue(key, value, 'exclude');
}}
onPinClick={(key, value) => toggleFilterPin(key, value)}
isPinned={(key, value) => isFilterPinned(key, value)}
onFieldPinClick={key => toggleFieldPin(key)}
isFieldPinned={key => isFieldPinned(key)}
onColumnToggle={onColumnToggle}
displayedColumns={displayedColumns}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={group.children.reduce(
(acc, child) => {
acc[child.key] = loadMoreLoadingKeys.has(child.key);
return acc;
},
{} as Record<string, boolean>,
)}
hasLoadedMore={group.children.reduce(
(acc, child) => {
acc[child.key] = Boolean(extraFacets[child.key]);
return acc;
},
{} as Record<string, boolean>,
)}
isDefaultExpanded={
// open by default if has selected values or pinned children
group.children.some(
child =>
(filterState[child.key] &&
(filterState[child.key].included.size > 0 ||
filterState[child.key].excluded.size > 0)) ||
isFieldPinned(child.key),
)
}
chartConfig={chartConfig}
isLive={isLive}
/>
))}
return (
<>
{/* Render grouped facets as nested filter groups */}
{grouped.map(group => (
<NestedFilterGroup
key={group.key}
data-testid={`nested-filter-group-${group.key}`}
name={group.key}
childFilters={group.children}
selectedValues={group.children.reduce(
(acc, child) => {
acc[child.key] = filterState[child.key]
? filterState[child.key]
: { included: new Set(), excluded: new Set() };
return acc;
},
{} as Record<
string,
{
included: Set<string | boolean>;
excluded: Set<string | boolean>;
}
>,
)}
onChange={(key, value) => {
setFilterValue(key, value);
}}
onClearClick={key => clearFilter(key)}
onOnlyClick={(key, value) => {
setFilterValue(key, value, 'only');
}}
onExcludeClick={(key, value) => {
setFilterValue(key, value, 'exclude');
}}
onPinClick={(key, value) => toggleFilterPin(key, value)}
isPinned={(key, value) => isFilterPinned(key, value)}
onFieldPinClick={key => toggleFieldPin(key)}
isFieldPinned={key => isFieldPinned(key)}
onColumnToggle={onColumnToggle}
displayedColumns={displayedColumns}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={group.children.reduce(
(acc, child) => {
acc[child.key] = loadMoreLoadingKeys.has(child.key);
return acc;
},
{} as Record<string, boolean>,
)}
hasLoadedMore={group.children.reduce(
(acc, child) => {
acc[child.key] = Boolean(extraFacets[child.key]);
return acc;
},
{} as Record<string, boolean>,
)}
isDefaultExpanded={
// open by default if has selected values or pinned children
group.children.some(
child =>
(filterState[child.key] &&
(filterState[child.key].included.size > 0 ||
filterState[child.key].excluded.size > 0)) ||
isFieldPinned(child.key),
)
}
chartConfig={chartConfig}
isLive={isLive}
/>
))}
{/* Render non-grouped facets as regular filter groups */}
{nonGrouped.map(facet => (
<FilterGroup
key={facet.key}
data-testid={`filter-group-${facet.key}`}
name={cleanedFacetName(facet.key)}
options={facet.value.map(value => ({
value,
label: value.toString(),
}))}
optionsLoading={isFacetsLoading}
selectedValues={
filterState[facet.key]
? filterState[facet.key]
: { included: new Set(), excluded: new Set() }
}
onChange={value => {
setFilterValue(facet.key, value);
}}
onClearClick={() => clearFilter(facet.key)}
onOnlyClick={value => {
setFilterValue(facet.key, value, 'only');
}}
onExcludeClick={value => {
setFilterValue(facet.key, value, 'exclude');
}}
onPinClick={value => toggleFilterPin(facet.key, value)}
isPinned={value => isFilterPinned(facet.key, value)}
onFieldPinClick={() => toggleFieldPin(facet.key)}
isFieldPinned={isFieldPinned(facet.key)}
onColumnToggle={
onColumnToggle
? () => onColumnToggle(facet.key)
: undefined
}
isColumnDisplayed={displayedColumns?.includes(facet.key)}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={loadMoreLoadingKeys.has(facet.key)}
hasLoadedMore={Boolean(extraFacets[facet.key])}
isDefaultExpanded={
// open by default if PK, or has selected values
isFieldPrimary(tableMetadata, facet.key) ||
isFieldPinned(facet.key) ||
(filterState[facet.key] &&
(filterState[facet.key].included.size > 0 ||
filterState[facet.key].excluded.size > 0 ||
filterState[facet.key].range != null))
}
chartConfig={chartConfig}
isLive={isLive}
onRangeChange={range => setFilterRange(facet.key, range)}
/>
))}
</>
);
})()}
{/* Render non-grouped facets as regular filter groups */}
{nonGrouped.map(facet => (
<FilterGroup
key={facet.key}
data-testid={`filter-group-${facet.key}`}
name={cleanedFacetName(facet.key)}
options={facet.value.map(value => ({
value,
label: value.toString(),
}))}
optionsLoading={isFacetsLoading}
selectedValues={
filterState[facet.key]
? filterState[facet.key]
: { included: new Set(), excluded: new Set() }
}
onChange={value => {
setFilterValue(facet.key, value);
}}
onClearClick={() => clearFilter(facet.key)}
onOnlyClick={value => {
setFilterValue(facet.key, value, 'only');
}}
onExcludeClick={value => {
setFilterValue(facet.key, value, 'exclude');
}}
onPinClick={value => toggleFilterPin(facet.key, value)}
isPinned={value => isFilterPinned(facet.key, value)}
onFieldPinClick={() => toggleFieldPin(facet.key)}
isFieldPinned={isFieldPinned(facet.key)}
onColumnToggle={
onColumnToggle ? () => onColumnToggle(facet.key) : undefined
}
isColumnDisplayed={displayedColumns?.includes(facet.key)}
onLoadMore={loadMoreFilterValuesForKey}
loadMoreLoading={loadMoreLoadingKeys.has(facet.key)}
hasLoadedMore={Boolean(extraFacets[facet.key])}
isDefaultExpanded={
// open by default if PK, or has selected values
isFieldPrimary(tableMetadata, facet.key) ||
isFieldPinned(facet.key) ||
(filterState[facet.key] &&
(filterState[facet.key].included.size > 0 ||
filterState[facet.key].excluded.size > 0 ||
filterState[facet.key].range != null))
}
chartConfig={chartConfig}
isLive={isLive}
onRangeChange={range => setFilterRange(facet.key, range)}
/>
))}
</>
<Button
variant="secondary"

View file

@ -40,17 +40,17 @@ export default function EmptyState({
}: EmptyStateProps) {
const inner = (
<Stack align="center" gap="xs">
{icon && (
{!!icon && (
<ThemeIcon size={56} radius="xl" variant="light" color="gray">
{icon}
</ThemeIcon>
)}
{title && (
{!!title && (
<Title order={3} fw={600} size="xl" maw={600}>
{title}
</Title>
)}
{description && (
{!!description && (
<Text size="sm" c="dimmed" ta="center" maw={600}>
{description}
</Text>

View file

@ -148,7 +148,7 @@ export const SectionWrapper: React.FC<
React.PropsWithChildren<{ title?: React.ReactNode }>
> = ({ children, title }) => (
<div className={styles.panelSectionWrapper}>
{title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
{!!title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
{children}
</div>
);

View file

@ -114,7 +114,7 @@ export const TableCellButton: React.FC<{
}> = ({ onClick, title, label, biIcon }) => {
return (
<button className={styles.tableCellButton} title={title} onClick={onClick}>
{label && <span>{label}</span>}
{!!label && <span>{label}</span>}
{biIcon === 'chevron-up' && <IconChevronUp size={14} />}
{biIcon === 'chevron-down' && <IconChevronDown size={14} />}
</button>