diff --git a/.changeset/old-yaks-win.md b/.changeset/old-yaks-win.md new file mode 100644 index 00000000000..b14c8089be9 --- /dev/null +++ b/.changeset/old-yaks-win.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Add `leadingVisual` prop to `UnderlineNav.Item` diff --git a/packages/react/src/UnderlineNav/UnderlineNav.docs.json b/packages/react/src/UnderlineNav/UnderlineNav.docs.json index 622a5c7dd59..dfae6bf8b6a 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.docs.json +++ b/packages/react/src/UnderlineNav/UnderlineNav.docs.json @@ -83,7 +83,14 @@ "name": "icon", "type": "Component", "defaultValue": "", - "description": "The leading icon comes before item label" + "description": "The leading icon comes before item label", + "deprecated": true + }, + { + "name": "leadingVisual", + "type": "React.ReactElement", + "defaultValue": "", + "description": "The leading visual comes before item label" }, { "name": "onSelect", @@ -103,4 +110,5 @@ } } ] -} \ No newline at end of file +} + diff --git a/packages/react/src/UnderlineNav/UnderlineNav.examples.stories.tsx b/packages/react/src/UnderlineNav/UnderlineNav.examples.stories.tsx index 9efc347f8cf..180c7730e30 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.examples.stories.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.examples.stories.tsx @@ -1,5 +1,4 @@ import React from 'react' -import type {IconProps} from '@primer/octicons-react' import { CodeIcon, IssueOpenedIcon, @@ -23,7 +22,6 @@ import { import type {Meta} from '@storybook/react-vite' import {UnderlineNav} from './index' import {Avatar, Button, Heading, Link, Text, StateLabel, BranchName} from '..' -import Octicon from '../Octicon' import classes from './UnderlineNav.examples.stories.module.css' export default { @@ -49,16 +47,16 @@ export const PullRequestPage = () => { - + } counter="0" aria-current="page"> Conversation - + }> Commits - + }> Checks - + }> Files Changes @@ -66,16 +64,16 @@ export const PullRequestPage = () => { ) } -const items: {navigation: string; icon: React.FC; counter?: number | string; href?: string}[] = [ - {navigation: 'Code', icon: CodeIcon, href: '#code'}, - {navigation: 'Issues', icon: IssueOpenedIcon, counter: '12K', href: '#issues'}, - {navigation: 'Pull Requests', icon: GitPullRequestIcon, counter: 13, href: '#pull-requests'}, - {navigation: 'Discussions', icon: CommentDiscussionIcon, counter: 5, href: '#discussions'}, - {navigation: 'Actions', icon: PlayIcon, counter: 4, href: '#actions'}, - {navigation: 'Projects', icon: ProjectIcon, counter: 9, href: '#projects'}, - {navigation: 'Insights', icon: GraphIcon, counter: '0', href: '#insights'}, - {navigation: 'Settings', icon: GearIcon, counter: 10, href: '#settings'}, - {navigation: 'Security', icon: ShieldLockIcon, href: '#security'}, +const items: {navigation: string; icon: React.ReactElement; counter?: number | string; href?: string}[] = [ + {navigation: 'Code', icon: , href: '#code'}, + {navigation: 'Issues', icon: , counter: '12K', href: '#issues'}, + {navigation: 'Pull Requests', icon: , counter: 13, href: '#pull-requests'}, + {navigation: 'Discussions', icon: , counter: 5, href: '#discussions'}, + {navigation: 'Actions', icon: , counter: 4, href: '#actions'}, + {navigation: 'Projects', icon: , counter: 9, href: '#projects'}, + {navigation: 'Insights', icon: , counter: '0', href: '#insights'}, + {navigation: 'Settings', icon: , counter: 10, href: '#settings'}, + {navigation: 'Security', icon: , href: '#security'}, ] export const ReposPage = () => { @@ -86,7 +84,7 @@ export const ReposPage = () => { {items.map((item, index) => ( { event.preventDefault() @@ -102,13 +100,13 @@ export const ReposPage = () => { ) } -const profileItems: {navigation: string; icon: React.FC; counter?: number | string; href?: string}[] = [ - {navigation: 'Overview', icon: BookIcon, href: '#overview'}, - {navigation: 'Repositories', icon: RepoIcon, counter: '12', href: '#repositories'}, - {navigation: 'Projects', icon: ProjectIcon, counter: 3, href: '#projects'}, - {navigation: 'Packages', icon: PackageIcon, counter: '0', href: '#packages'}, - {navigation: 'Stars', icon: StarIcon, counter: '0', href: '#stars'}, - {navigation: 'Activity', icon: ThreeBarsIcon, counter: 67, href: '#activity'}, +const profileItems: {navigation: string; icon: React.ReactElement; counter?: number | string; href?: string}[] = [ + {navigation: 'Overview', icon: , href: '#overview'}, + {navigation: 'Repositories', icon: , counter: '12', href: '#repositories'}, + {navigation: 'Projects', icon: , counter: 3, href: '#projects'}, + {navigation: 'Packages', icon: , counter: '0', href: '#packages'}, + {navigation: 'Stars', icon: , counter: '0', href: '#stars'}, + {navigation: 'Activity', icon: , counter: 67, href: '#activity'}, ] export const ProfilePage = () => { @@ -120,7 +118,7 @@ export const ProfilePage = () => { {profileItems.map((item, index) => ( { event.preventDefault() @@ -151,7 +149,7 @@ export const ProfilePage = () => {
- + 47 Followers diff --git a/packages/react/src/UnderlineNav/UnderlineNav.features.stories.tsx b/packages/react/src/UnderlineNav/UnderlineNav.features.stories.tsx index ffbc8a18cb8..6b35a2c739b 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.features.stories.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.features.stories.tsx @@ -1,5 +1,4 @@ import React from 'react' -import type {IconProps} from '@primer/octicons-react' import { EyeIcon, CodeIcon, @@ -35,17 +34,17 @@ export const Default = () => { export const WithIcons = () => { return ( - }>Code - } counter={6}> + }>Code + } counter={6}> Issues - }> + }> Pull Requests - } counter={7}> + } counter={7}> Discussions - }>Projects + }>Projects ) } @@ -53,26 +52,26 @@ export const WithIcons = () => { export const WithCounterLabels = () => { return ( - } counter="11K"> + } counter="11K"> Code - } counter={12}> + } counter={12}> Issues ) } -const items: {navigation: string; icon: React.FC; counter?: number | string; href?: string}[] = [ - {navigation: 'Code', icon: CodeIcon, href: '#code'}, - {navigation: 'Issues', icon: IssueOpenedIcon, counter: '12K', href: '#issues'}, - {navigation: 'Pull Requests', icon: GitPullRequestIcon, counter: 13, href: '#pull-requests'}, - {navigation: 'Discussions', icon: CommentDiscussionIcon, counter: 5, href: '#discussions'}, - {navigation: 'Actions', icon: PlayIcon, counter: 4, href: '#actions'}, - {navigation: 'Projects', icon: ProjectIcon, counter: 9, href: '#projects'}, - {navigation: 'Insights', icon: GraphIcon, counter: '0', href: '#insights'}, - {navigation: 'Settings', icon: GearIcon, counter: 10, href: '#settings'}, - {navigation: 'Security', icon: ShieldLockIcon, href: '#security'}, +const items: {navigation: string; icon: React.ReactElement; counter?: number | string; href?: string}[] = [ + {navigation: 'Code', icon: , href: '#code'}, + {navigation: 'Issues', icon: , counter: '12K', href: '#issues'}, + {navigation: 'Pull Requests', icon: , counter: 13, href: '#pull-requests'}, + {navigation: 'Discussions', icon: , counter: 5, href: '#discussions'}, + {navigation: 'Actions', icon: , counter: 4, href: '#actions'}, + {navigation: 'Projects', icon: , counter: 9, href: '#projects'}, + {navigation: 'Insights', icon: , counter: '0', href: '#insights'}, + {navigation: 'Settings', icon: , counter: 10, href: '#settings'}, + {navigation: 'Security', icon: , href: '#security'}, ] export const OverflowTemplate = ({initialSelectedIndex = 1}: {initialSelectedIndex?: number}) => { @@ -86,7 +85,7 @@ export const OverflowTemplate = ({initialSelectedIndex = 1}: {initialSelectedInd {items.map((item, index) => ( { {items.map((item, index) => ( setSelectedIndex(index)} counter={item.counter} diff --git a/packages/react/src/UnderlineNav/UnderlineNav.figma.tsx b/packages/react/src/UnderlineNav/UnderlineNav.figma.tsx index 4be0b34472e..b8b08eb6ecb 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.figma.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.figma.tsx @@ -33,7 +33,7 @@ figma.connect( }), }, example: ({label, current, counter, leadingVisual}) => ( - + {label} ), diff --git a/packages/react/src/UnderlineNav/UnderlineNav.test.tsx b/packages/react/src/UnderlineNav/UnderlineNav.test.tsx index e3b5b99b585..53c0eb07532 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.test.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.test.tsx @@ -2,7 +2,6 @@ import {describe, expect, it, vi} from 'vitest' import type React from 'react' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' -import type {IconProps} from '@primer/octicons-react' import { CodeIcon, IssueOpenedIcon, @@ -24,16 +23,16 @@ const ResponsiveUnderlineNav = ({ loadingCounters?: boolean displayExtraEl?: boolean }) => { - const items: {navigation: string; icon?: React.FC; counter?: number}[] = [ - {navigation: 'Code', icon: CodeIcon}, - {navigation: 'Issues', icon: IssueOpenedIcon, counter: 120}, - {navigation: 'Pull Requests', icon: GitPullRequestIcon, counter: 13}, - {navigation: 'Discussions', icon: CommentDiscussionIcon, counter: 5}, + const items: {navigation: string; icon?: React.ReactElement; counter?: number}[] = [ + {navigation: 'Code', icon: }, + {navigation: 'Issues', icon: , counter: 120}, + {navigation: 'Pull Requests', icon: , counter: 13}, + {navigation: 'Discussions', icon: , counter: 5}, {navigation: 'Actions', counter: 4}, - {navigation: 'Projects', icon: ProjectIcon, counter: 9}, - {navigation: 'Insights', icon: GraphIcon}, + {navigation: 'Projects', icon: , counter: 9}, + {navigation: 'Insights', icon: }, {navigation: 'Settings', counter: 10}, - {navigation: 'Security', icon: ShieldLockIcon}, + {navigation: 'Security', icon: }, ] return ( @@ -42,7 +41,7 @@ const ResponsiveUnderlineNav = ({ {items.map(item => ( @@ -168,11 +167,13 @@ describe('UnderlineNav', () => { it('should support icons passed in as an element', () => { render( - }> + }> Page one - }>Page two - }>Page three + }>Page two + }> + Page three + , ) @@ -191,6 +192,20 @@ describe('UnderlineNav', () => { expect(item).toHaveClass('custom-class') expect(item.className).toContain('UnderlineItem') }) + + it('supports the deprecated `icon` prop', () => { + render( + + }>as jsx element + }> + as functional component + + , + ) + + expect(screen.getByTestId('jsx-element')).toBeInTheDocument() + expect(screen.getByTestId('functional-component')).toBeInTheDocument() + }) }) describe('Keyboard Navigation', () => { diff --git a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx index dde70d1c9f9..3341f677e11 100644 --- a/packages/react/src/UnderlineNav/UnderlineNavItem.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNavItem.tsx @@ -25,23 +25,34 @@ export type UnderlineNavItemProps = { * Primary content for an UnderlineNav */ children?: React.ReactNode + /** * Callback that will trigger both on click selection and keyboard selection. */ onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void + /** * Is `UnderlineNav.Item` current page? */ 'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean + /** * Icon before the text + * @deprecated Use the `leadingVisual` prop instead */ // eslint-disable-next-line @typescript-eslint/no-explicit-any icon?: React.FunctionComponent | React.ReactElement + + /** + * Render a visual before the text + */ + leadingVisual?: React.ReactElement + /** * Renders `UnderlineNav.Item` as given component i.e. react-router's Link **/ as?: React.ElementType | 'a' + /** * Counter */ @@ -50,7 +61,17 @@ export type UnderlineNavItemProps = { export const UnderlineNavItem = forwardRef( ( - {as: Component = 'a', href = '#', children, counter, onSelect, 'aria-current': ariaCurrent, icon: Icon, ...props}, + { + as: Component = 'a', + href = '#', + children, + counter, + onSelect, + 'aria-current': ariaCurrent, + icon: Icon, + leadingVisual, + ...props + }, forwardedRef, ) => { const backupRef = useRef(null) @@ -108,7 +129,7 @@ export const UnderlineNavItem = forwardRef( onKeyDown={keyDownHandler} onClick={clickHandler} counter={counter} - icon={Icon} + icon={leadingVisual ?? Icon} loadingCounters={loadingCounters} iconsVisible={iconsVisible} {...props}