Skip to content

Commit 9113e76

Browse files
feat: improve icon name formatting (#2693)
- feat: improves icon name formatting by preserving correct icon names. Correct names such as `ChevronRight` where formatted wrongly - chore: update lucide-react to include latest icons <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Improved the Icon component’s name formatting so icons resolve correctly from kebab-case, lowercase, PascalCase, uppercase, and mixed-case inputs. Updated lucide-react to the latest version for new icons. - **Bug Fixes** - Updated formatIconName to preserve PascalCase, normalize kebab-case to PascalCase, and clean up all-uppercase and mixed-case names. - Added tests for kebab-case, lowercase, PascalCase, uppercase, mixed-case, invalid names (warn + null), and prop forwarding. - **Dependencies** - Bumped lucide-react from 0.468.0 to 0.555.0. <sup>Written for commit 0224c5e. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Josh Hayes <35790761+hayescode@users.noreply.github.com>
1 parent 4312b18 commit 9113e76

File tree

4 files changed

+75
-13
lines changed

4 files changed

+75
-13
lines changed

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"highlight.js": "^11.9.0",
4444
"i18next": "^23.7.16",
4545
"lodash": "^4.17.21",
46-
"lucide-react": "^0.468.0",
46+
"lucide-react": "^0.555.0",
4747
"plotly.js": "^2.27.0",
4848
"react": "^18.3.1",
4949
"react-dom": "^18.3.1",

frontend/pnpm-lock.yaml

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/Icon.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@ interface Props {
88
}
99

1010
const Icon = ({ name, ...props }: Props) => {
11-
// Convert the name to proper case (e.g., "plus" -> "Plus", "chevron-right" -> "ChevronRight")
12-
const formatIconName = (str: string): string => {
13-
return str
14-
.split('-')
15-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
16-
.join('');
11+
// Convert the name to proper case
12+
const formatIconName = (name: string): string => {
13+
//aggressively lowercase the parts to clean up inputs like "ChEvRoN-rIgHt"
14+
if (name.includes('-')) {
15+
return name
16+
.split('-')
17+
.map(
18+
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
19+
)
20+
.join('');
21+
}
22+
if (name === name.toUpperCase()) {
23+
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
24+
}
25+
return name.charAt(0).toUpperCase() + name.slice(1);
1726
};
1827

1928
// Try to get the icon component using the formatted name

frontend/tests/icon.spec.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { render } from '@testing-library/react';
2+
import { describe, expect, it, vi } from 'vitest';
3+
4+
import Icon from '@/components/Icon';
5+
6+
describe('Icon component', () => {
7+
it('renders icon with kebab-case name', () => {
8+
const { container } = render(<Icon name="chevron-right" />);
9+
expect(container.querySelector('svg')).toBeInTheDocument();
10+
});
11+
12+
it('renders icon with lowercase name', () => {
13+
const { container } = render(<Icon name="plus" />);
14+
expect(container.querySelector('svg')).toBeInTheDocument();
15+
});
16+
17+
it('renders icon with PascalCase name', () => {
18+
const { container } = render(<Icon name="ChevronRight" />);
19+
expect(container.querySelector('svg')).toBeInTheDocument();
20+
});
21+
22+
it('renders icon with all uppercase name', () => {
23+
const { container } = render(<Icon name="PLUS" />);
24+
expect(container.querySelector('svg')).toBeInTheDocument();
25+
});
26+
27+
it('renders icon with mixed case name', () => {
28+
const { container } = render(<Icon name="ChEvRoN-rIgHt" />);
29+
expect(container.querySelector('svg')).toBeInTheDocument();
30+
});
31+
32+
it('returns null and warns for invalid icon name', () => {
33+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
34+
const { container } = render(<Icon name="invalid-icon-name" />);
35+
36+
expect(container.firstChild).toBeNull();
37+
expect(consoleSpy).toHaveBeenCalledWith(
38+
'Icon "invalid-icon-name" not found in Lucide icons'
39+
);
40+
41+
consoleSpy.mockRestore();
42+
});
43+
44+
it('passes props to the icon component', () => {
45+
const { container } = render(
46+
<Icon name="home" size={24} color="red" className="test-class" />
47+
);
48+
const svg = container.querySelector('svg');
49+
50+
expect(svg).toBeInTheDocument();
51+
expect(svg).toHaveClass('test-class');
52+
});
53+
});

0 commit comments

Comments
 (0)