diff --git a/.changeset/giant-loops-send.md b/.changeset/giant-loops-send.md
new file mode 100644
index 00000000000..9b0a22a6979
--- /dev/null
+++ b/.changeset/giant-loops-send.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+Add leadingVisual to InlineMessage component.
diff --git a/packages/react/src/InlineMessage/InlineMessage.docs.json b/packages/react/src/InlineMessage/InlineMessage.docs.json
index eaa28ca23dd..27ec3bd3f8e 100644
--- a/packages/react/src/InlineMessage/InlineMessage.docs.json
+++ b/packages/react/src/InlineMessage/InlineMessage.docs.json
@@ -37,6 +37,12 @@
"description": "Specify the type of the inline message",
"type": "'critical' | 'success' | 'unvailable' | 'warning'",
"required": true
+ },
+ {
+ "name": "leadingVisual",
+ "description": "A custom leading visual to display instead of the default variant icon.",
+ "type": "React.ElementType | React.ReactNode",
+ "required": false
}
]
-}
+}
\ No newline at end of file
diff --git a/packages/react/src/InlineMessage/InlineMessage.stories.tsx b/packages/react/src/InlineMessage/InlineMessage.stories.tsx
index 23d190488c1..6dabc253fa6 100644
--- a/packages/react/src/InlineMessage/InlineMessage.stories.tsx
+++ b/packages/react/src/InlineMessage/InlineMessage.stories.tsx
@@ -1,4 +1,14 @@
import type {Meta, StoryObj} from '@storybook/react-vite'
+import {
+ AlertIcon,
+ CheckCircleIcon,
+ InfoIcon,
+ LockIcon,
+ RocketIcon,
+ XCircleIcon,
+ HeartIcon,
+ StarIcon,
+} from '@primer/octicons-react'
import {InlineMessage} from '../InlineMessage'
const meta = {
@@ -12,9 +22,27 @@ export const Default = () => {
return An example inline message
}
+const iconMap = {
+ default: undefined,
+ InfoIcon,
+ LockIcon,
+ RocketIcon,
+ AlertIcon,
+ CheckCircleIcon,
+ XCircleIcon,
+ HeartIcon,
+ StarIcon,
+} as const
+
export const Playground: StoryObj = {
render(args) {
- return An example inline message
+ const {leadingVisual: leadingVisualOption, ...rest} = args
+ const leadingVisual = leadingVisualOption ? iconMap[leadingVisualOption as keyof typeof iconMap] : undefined
+ return (
+
+ An example inline message
+
+ )
},
argTypes: {
size: {
@@ -29,9 +57,18 @@ export const Playground: StoryObj = {
},
options: ['critical', 'success', 'unavailable', 'warning'],
},
+ leadingVisual: {
+ name: 'leadingVisual',
+ control: {
+ type: 'select',
+ },
+ options: Object.keys(iconMap),
+ description: 'Select a custom icon to override the default variant icon',
+ },
},
args: {
size: 'medium',
variant: 'success',
+ leadingVisual: 'default',
},
}
diff --git a/packages/react/src/InlineMessage/InlineMessage.test.tsx b/packages/react/src/InlineMessage/InlineMessage.test.tsx
index b7448da6b10..4678a2e5ded 100644
--- a/packages/react/src/InlineMessage/InlineMessage.test.tsx
+++ b/packages/react/src/InlineMessage/InlineMessage.test.tsx
@@ -1,6 +1,8 @@
import {render, screen} from '@testing-library/react'
import {describe, expect, it, test} from 'vitest'
+import {InfoIcon} from '@primer/octicons-react'
import {InlineMessage} from '../InlineMessage'
+import React from 'react'
describe('InlineMessage', () => {
it('should render content passed as `children`', () => {
@@ -79,4 +81,41 @@ describe('InlineMessage', () => {
)
expect(screen.getByTestId('container')).toHaveAttribute('data-variant', 'warning')
})
+
+ it('should render leading visual', () => {
+ render(
+ <>
+ }>
+ test with custom icon
+
+ (
+ leadingVisual
+ ))}
+ >
+ test with memo icon
+
+ (
+ leadingVisual
+ ))}
+ >
+ test with forward ref icon
+
+ >,
+ )
+ expect(screen.getByTestId('info-icon')).toBeInTheDocument()
+ expect(screen.getByTestId('memo')).toBeInTheDocument()
+ expect(screen.getByTestId('forward-ref')).toBeInTheDocument()
+ })
+
+ it('should use default icon when `leadingVisual` is not provided', () => {
+ const {container} = render(test with default icon)
+ expect(screen.getByText('test with default icon')).toBeInTheDocument()
+ // Default icon should be rendered
+ const svg = container.querySelector('svg')
+ expect(svg).toBeInTheDocument()
+ })
})
diff --git a/packages/react/src/InlineMessage/InlineMessage.tsx b/packages/react/src/InlineMessage/InlineMessage.tsx
index 002f0577533..307f17e82d7 100644
--- a/packages/react/src/InlineMessage/InlineMessage.tsx
+++ b/packages/react/src/InlineMessage/InlineMessage.tsx
@@ -1,6 +1,7 @@
import {AlertFillIcon, AlertIcon, CheckCircleFillIcon, CheckCircleIcon} from '@primer/octicons-react'
import {clsx} from 'clsx'
import type React from 'react'
+import {isValidElementType} from 'react-is'
import classes from './InlineMessage.module.css'
type MessageVariant = 'critical' | 'success' | 'unavailable' | 'warning'
@@ -14,6 +15,11 @@ export type InlineMessageProps = React.ComponentPropsWithoutRef<'div'> & {
* Specify the type of the InlineMessage
*/
variant: MessageVariant
+
+ /**
+ * A custom leading visual (icon or other element) to display instead of the default variant icon.
+ */
+ leadingVisual?: React.ElementType | React.ReactNode
}
const icons: Record = {
@@ -30,8 +36,26 @@ const smallIcons: Record = {
unavailable: ,
}
-export function InlineMessage({children, className, size = 'medium', variant, ...rest}: InlineMessageProps) {
- const icon = size === 'small' ? smallIcons[variant] : icons[variant]
+export function InlineMessage({
+ children,
+ className,
+ size = 'medium',
+ variant,
+ leadingVisual: LeadingVisual,
+ ...rest
+}: InlineMessageProps) {
+ let icon: React.ReactNode
+
+ if (LeadingVisual !== undefined) {
+ if (typeof LeadingVisual !== 'string' && isValidElementType(LeadingVisual)) {
+ icon =
+ } else {
+ icon = LeadingVisual
+ }
+ } else {
+ // Use default icon based on variant and size
+ icon = size === 'small' ? smallIcons[variant] : icons[variant]
+ }
return (