Skip to content

Commit c5d0e9c

Browse files
committed
lint fix
1 parent 5a998c0 commit c5d0e9c

File tree

6 files changed

+123
-103
lines changed

6 files changed

+123
-103
lines changed

src/elements/dropdown/__docs__/stories/navigationalDropdown.stories.tsx

Lines changed: 92 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import styled from 'styled-components';
44

55
import {palette} from '../../../../helpers/colorHelpers';
66
import {EmptyState} from '../../../emptyState/emptyState';
7+
import {NavigationalDropdownContainer} from '../../components/NavigationalDropdownContainer';
78
import {NavigationalDropdownProvider} from '../../context/NavigationalDropdownContext';
89
import {Dropdown} from '../../dropdown';
910
import {DropdownButton} from '../../dropdownButton';
@@ -12,7 +13,6 @@ import {DropdownHeader} from '../../dropdownHeader';
1213
import {DropdownHeading} from '../../dropdownHeading';
1314
import {DropdownItem} from '../../dropdownItem';
1415
import {DropdownItemIcon} from '../../dropdownItemIcon';
15-
import {NavigationalDropdownContainer} from '../../components/NavigationalDropdownContainer';
1616

1717
const StyledWrapperDiv = styled.div`
1818
display: flex;
@@ -63,6 +63,42 @@ const ticketStatusesData = [
6363
/**
6464
* Basic navigational dropdown example
6565
*/
66+
// Inbox item component
67+
const InboxDropdownItem: React.FC<{
68+
inbox: {id: string; name: string; icon: string; isPrivate?: boolean};
69+
isSelected: boolean;
70+
onToggle: () => void;
71+
color: string;
72+
}> = ({inbox, isSelected, onToggle, color}) => (
73+
<DropdownItem key={inbox.id} type="multi" isSelected={isSelected} onClick={onToggle}>
74+
<DropdownItemIcon color={color} iconName="Archive" />
75+
{inbox.name}
76+
</DropdownItem>
77+
);
78+
79+
// Tag item component
80+
const TagDropdownItem: React.FC<{
81+
tag: {id: string; name: string; color: string};
82+
isSelected: boolean;
83+
onToggle: () => void;
84+
}> = ({tag, isSelected, onToggle}) => (
85+
<DropdownItem key={tag.id} type="multi" isSelected={isSelected} onClick={onToggle}>
86+
<DropdownItemIcon color={tag.color} iconName="Star" />
87+
{tag.name}
88+
</DropdownItem>
89+
);
90+
91+
// Assignee item component
92+
const AssigneeDropdownItem: React.FC<{
93+
assignee: {id: string; name: string; email: string};
94+
isSelected: boolean;
95+
onToggle: () => void;
96+
}> = ({assignee, isSelected, onToggle}) => (
97+
<DropdownItem key={assignee.id} type="multi" isSelected={isSelected} onClick={onToggle} description={assignee.email}>
98+
{assignee.name}
99+
</DropdownItem>
100+
);
101+
66102
const BasicNavigationalTemplate: StoryFn = () => {
67103
const [selectedFilters, setSelectedFilters] = useState<Record<string, string[]>>({
68104
inboxes: [],
@@ -90,18 +126,61 @@ const BasicNavigationalTemplate: StoryFn = () => {
90126
return items.filter((item) => item.name.toLowerCase().includes(search.toLowerCase()));
91127
};
92128

93-
// Helper to render a category section with heading (only if items exist)
94-
const renderCategorySection = <T extends {id: string; name: string}>(
129+
// Helper to render inbox sections
130+
const renderInboxSection = (
95131
heading: string,
96-
items: T[],
97-
category: string,
98-
renderItem: (item: T) => React.ReactNode
132+
inboxes: typeof inboxesData,
133+
color: string
99134
) => {
100-
if (items.length === 0) return null;
135+
if (inboxes.length === 0) return null;
101136
return (
102137
<>
103138
<DropdownHeading>{heading}</DropdownHeading>
104-
{items.map((item) => renderItem(item))}
139+
{inboxes.map((inbox) => (
140+
<InboxDropdownItem
141+
key={inbox.id}
142+
inbox={inbox}
143+
isSelected={selectedFilters.inboxes.includes(inbox.id)}
144+
onToggle={() => toggleFilter('inboxes', inbox.id)}
145+
color={color}
146+
/>
147+
))}
148+
</>
149+
);
150+
};
151+
152+
// Helper to render tag sections
153+
const renderTagSection = (heading: string, tags: typeof tagsData) => {
154+
if (tags.length === 0) return null;
155+
return (
156+
<>
157+
<DropdownHeading>{heading}</DropdownHeading>
158+
{tags.map((tag) => (
159+
<TagDropdownItem
160+
key={tag.id}
161+
tag={tag}
162+
isSelected={selectedFilters.tags.includes(tag.id)}
163+
onToggle={() => toggleFilter('tags', tag.id)}
164+
/>
165+
))}
166+
</>
167+
);
168+
};
169+
170+
// Helper to render assignee sections
171+
const renderAssigneeSection = (heading: string, assignees: typeof assigneesData) => {
172+
if (assignees.length === 0) return null;
173+
return (
174+
<>
175+
<DropdownHeading>{heading}</DropdownHeading>
176+
{assignees.map((assignee) => (
177+
<AssigneeDropdownItem
178+
key={assignee.id}
179+
assignee={assignee}
180+
isSelected={selectedFilters.assignees.includes(assignee.id)}
181+
onToggle={() => toggleFilter('assignees', assignee.id)}
182+
/>
183+
))}
105184
</>
106185
);
107186
};
@@ -114,36 +193,9 @@ const BasicNavigationalTemplate: StoryFn = () => {
114193

115194
return (
116195
<>
117-
{renderCategorySection('Any', anyInboxes, 'inboxes', (inbox) => (
118-
<DropdownItem
119-
key={inbox.id}
120-
type="multi"
121-
isSelected={selectedFilters.inboxes.includes(inbox.id)}
122-
onClick={() => toggleFilter('inboxes', inbox.id)}>
123-
<DropdownItemIcon color={palette.blue.shade40} iconName="Archive" />
124-
{inbox.name}
125-
</DropdownItem>
126-
))}
127-
{renderCategorySection('Individual', individualInboxes, 'inboxes', (inbox) => (
128-
<DropdownItem
129-
key={inbox.id}
130-
type="multi"
131-
isSelected={selectedFilters.inboxes.includes(inbox.id)}
132-
onClick={() => toggleFilter('inboxes', inbox.id)}>
133-
<DropdownItemIcon color={palette.green.shade40} iconName="Archive" />
134-
{inbox.name}
135-
</DropdownItem>
136-
))}
137-
{renderCategorySection('Shared', sharedInboxes, 'inboxes', (inbox) => (
138-
<DropdownItem
139-
key={inbox.id}
140-
type="multi"
141-
isSelected={selectedFilters.inboxes.includes(inbox.id)}
142-
onClick={() => toggleFilter('inboxes', inbox.id)}>
143-
<DropdownItemIcon color={palette.purple.shade40} iconName="Archive" />
144-
{inbox.name}
145-
</DropdownItem>
146-
))}
196+
{renderInboxSection('Any', anyInboxes, palette.blue.shade40)}
197+
{renderInboxSection('Individual', individualInboxes, palette.green.shade40)}
198+
{renderInboxSection('Shared', sharedInboxes, palette.purple.shade40)}
147199
</>
148200
);
149201
};
@@ -187,16 +239,7 @@ const BasicNavigationalTemplate: StoryFn = () => {
187239
onSearchChange={(newValue) => setSearchValues((prev) => ({...prev, tags: newValue}))}>
188240
Tags
189241
</DropdownHeader>
190-
{renderCategorySection('Tags', getFilteredItems(tagsData, 'tags'), 'tags', (tag) => (
191-
<DropdownItem
192-
key={tag.id}
193-
type="multi"
194-
isSelected={selectedFilters.tags.includes(tag.id)}
195-
onClick={() => toggleFilter('tags', tag.id)}>
196-
<DropdownItemIcon color={tag.color} iconName="Star" />
197-
{tag.name}
198-
</DropdownItem>
199-
))}
242+
{renderTagSection('Tags', getFilteredItems(tagsData, 'tags'))}
200243
</Dropdown>
201244
}>
202245
<DropdownItemIcon color={palette.orange.shade40} iconName="Star" />
@@ -219,21 +262,7 @@ const BasicNavigationalTemplate: StoryFn = () => {
219262
onSearchChange={(newValue) => setSearchValues((prev) => ({...prev, assignees: newValue}))}>
220263
Assignees
221264
</DropdownHeader>
222-
{renderCategorySection(
223-
'Assignees',
224-
getFilteredItems(assigneesData, 'assignees'),
225-
'assignees',
226-
(assignee) => (
227-
<DropdownItem
228-
key={assignee.id}
229-
type="multi"
230-
isSelected={selectedFilters.assignees.includes(assignee.id)}
231-
onClick={() => toggleFilter('assignees', assignee.id)}
232-
description={assignee.email}>
233-
{assignee.name}
234-
</DropdownItem>
235-
)
236-
)}
265+
{renderAssigneeSection('Assignees', getFilteredItems(assigneesData, 'assignees'))}
237266
</Dropdown>
238267
}>
239268
<DropdownItemIcon color={palette.green.shade40} iconName="Calendar" />

src/elements/dropdown/components/NavigationalDropdownContainer.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, isValidElement, cloneElement, Children} from 'react';
1+
import React, {Children,cloneElement, isValidElement, useEffect} from 'react';
22

33
import {useNavigationalDropdown} from '../context/NavigationalDropdownContext';
44

@@ -7,32 +7,25 @@ interface NavigationalDropdownContainerProps {
77
}
88

99
const hasDisplayName = (element: React.ReactElement, name: string): boolean => {
10-
return Boolean(
11-
element.type &&
12-
typeof element.type === 'function' &&
13-
(element.type as {displayName?: string}).displayName === name
14-
);
10+
if (!element.type || typeof element.type !== 'function') return false;
11+
return 'displayName' in element.type && element.type.displayName === name;
1512
};
1613

1714
const injectBackClickIntoDropdownHeader = (
1815
children: React.ReactNode,
1916
handleBackClick: (event: React.MouseEvent) => void
20-
): React.ReactNode => {
21-
return Children.map(children, (child) => {
17+
): React.ReactNode => Children.map(children, (child) => {
2218
if (!isValidElement(child)) return child;
2319

2420
if (hasDisplayName(child, 'DropdownHeader')) {
25-
return cloneElement(
26-
child as React.ReactElement,
27-
{
28-
onBackClick: handleBackClick
29-
} as never
30-
);
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
return cloneElement(child as any, {
23+
onBackClick: handleBackClick
24+
});
3125
}
3226

3327
return child;
3428
});
35-
};
3629

3730
export const NavigationalDropdownContainer: React.FC<NavigationalDropdownContainerProps> = ({className}) => {
3831
const {currentContent, navigateBack, canNavigateBack, reset} = useNavigationalDropdown();
@@ -53,16 +46,16 @@ export const NavigationalDropdownContainer: React.FC<NavigationalDropdownContain
5346
const dropdownElement = currentContent;
5447

5548
if (hasDisplayName(dropdownElement, 'Dropdown')) {
56-
const props = dropdownElement.props as {children?: React.ReactNode; className?: string};
49+
const {children: dropdownChildren, className: propsClassName} = dropdownElement.props;
5750

58-
if (props.children) {
59-
const modifiedChildren = injectBackClickIntoDropdownHeader(props.children, handleBackClick);
51+
if (dropdownChildren) {
52+
const modifiedChildren = injectBackClickIntoDropdownHeader(dropdownChildren, handleBackClick);
6053

6154
return cloneElement(dropdownElement, {
62-
...props,
55+
...dropdownElement.props,
6356
children: modifiedChildren,
64-
className: className || props.className
65-
} as never);
57+
className: className || propsClassName
58+
});
6659
}
6760
}
6861
}

src/elements/dropdown/components/NavigationalSubmenuTrigger.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const StyledTriggerWrapper = styled.div`
2626
}
2727
`;
2828

29-
const isInteractiveElement = (target: HTMLElement): boolean => {
29+
const isInteractiveElement = (target: EventTarget | null): boolean => {
30+
if (!(target instanceof HTMLElement)) return false;
3031
return Boolean(
3132
target.closest(
3233
'input:not([type="checkbox"]):not([type="radio"]), textarea, select, [contenteditable="true"]'
@@ -52,19 +53,18 @@ export const NavigationalSubmenuTrigger: React.FC<NavigationalSubmenuTriggerProp
5253
navigateTo(submenuId, getSubmenu, backTitle);
5354
}
5455

55-
if (autoNavigateToSubmenuId !== submenuId) {
56+
if (autoNavigateToSubmenuId !== submenuId)
5657
hasAutoNavigatedRef.current = false;
57-
}
58+
5859
}, [autoNavigateToSubmenuId, submenuId, getSubmenu, backTitle, navigateTo]);
5960

6061
const handleClick = useCallback(
6162
(event: React.MouseEvent) => {
6263
if (disabled) return;
6364

64-
const target = event.target as HTMLElement;
65-
if (isInteractiveElement(target)) {
65+
if (isInteractiveElement(event.target))
6666
return;
67-
}
67+
6868

6969
event.preventDefault();
7070
event.stopPropagation();
@@ -79,10 +79,9 @@ export const NavigationalSubmenuTrigger: React.FC<NavigationalSubmenuTriggerProp
7979
(event: React.KeyboardEvent) => {
8080
if (disabled) return;
8181

82-
const target = event.target as HTMLElement;
83-
if (isInteractiveElement(target)) {
82+
if (isInteractiveElement(event.target))
8483
return;
85-
}
84+
8685

8786
if (event.key === 'Enter' || event.key === ' ') {
8887
event.preventDefault();

src/elements/dropdown/context/NavigationalDropdownContext.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {createContext, useCallback, useContext, useMemo, useState, useLayoutEffect} from 'react';
1+
import React, {createContext, useCallback, useContext, useLayoutEffect,useMemo, useState} from 'react';
22

33
export interface NavigationalView {
44
id: string;
@@ -49,7 +49,7 @@ export const NavigationalDropdownProvider: React.FC<NavigationalDropdownProvider
4949
const [autoNavigateToSubmenuId, setAutoNavigateToSubmenuId] = React.useState<string | null>(null);
5050

5151
useLayoutEffect(() => {
52-
if (prevContentVersionRef.current !== contentVersion && prevContentVersionRef.current !== undefined) {
52+
if (prevContentVersionRef.current !== contentVersion && prevContentVersionRef.current !== undefined)
5353
setViewStack((currentStack) => {
5454
const activeSubmenuId = currentStack.length > 1 ? currentStack[currentStack.length - 1].id : null;
5555

@@ -61,13 +61,13 @@ export const NavigationalDropdownProvider: React.FC<NavigationalDropdownProvider
6161
}
6262
];
6363

64-
if (activeSubmenuId && activeSubmenuId !== rootId) {
64+
if (activeSubmenuId && activeSubmenuId !== rootId)
6565
setAutoNavigateToSubmenuId(activeSubmenuId);
66-
}
66+
6767

6868
return newStack;
6969
});
70-
}
70+
7171
prevContentVersionRef.current = contentVersion;
7272
}, [contentVersion, getRootContent, rootId]);
7373

@@ -117,7 +117,7 @@ export const NavigationalDropdownProvider: React.FC<NavigationalDropdownProvider
117117

118118
const currentContent = useMemo(
119119
() => (viewStack.length > 0 ? viewStack[viewStack.length - 1].getContent() : null),
120-
[viewStack, contentVersion]
120+
[viewStack]
121121
);
122122

123123
const contextValue = useMemo<NavigationalDropdownContextValue>(

src/elements/dropdown/dropdown.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ export const Dropdown: FC<DropdownProps> = ({
170170
() =>
171171
React.Children.toArray(children).some((child) => {
172172
if (!isValidElement(child) || !child.props) return false;
173-
const childProps = child.props as Record<string, unknown>;
174-
return 'submenu' in childProps && Boolean(childProps.submenu);
173+
return 'submenu' in child.props && Boolean(child.props.submenu);
175174
}),
176175
[children]
177176
);

src/elements/dropdown/dropdownItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export const DropdownItem: FC<DropdownItemProps> = ({
169169

170170
// If we have a submenu, wrap with the appropriate trigger based on mode
171171
if (submenu) {
172-
if (submenuMode === 'navigational') {
172+
if (submenuMode === 'navigational')
173173
return (
174174
<NavigationalSubmenuTrigger
175175
submenuId={effectiveSubmenuId}
@@ -179,7 +179,7 @@ export const DropdownItem: FC<DropdownItemProps> = ({
179179
{content}
180180
</NavigationalSubmenuTrigger>
181181
);
182-
}
182+
183183

184184
// Default to hover mode
185185
return (

0 commit comments

Comments
 (0)