diff --git a/.changeset/brave-panthers-wave.md b/.changeset/brave-panthers-wave.md new file mode 100644 index 00000000000..bad45d68d25 --- /dev/null +++ b/.changeset/brave-panthers-wave.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +StateLabel: Add alert status variants (alertOpened, alertFixed, alertDismissed) with corresponding shield icons for displaying security alert states diff --git a/packages/react/src/StateLabel/StateLabel.docs.json b/packages/react/src/StateLabel/StateLabel.docs.json index e4edf2918fd..6d9d66e1329 100644 --- a/packages/react/src/StateLabel/StateLabel.docs.json +++ b/packages/react/src/StateLabel/StateLabel.docs.json @@ -37,6 +37,15 @@ { "id": "components-statelabel-features--unavailable" }, + { + "id": "components-statelabel-features--alert-opened" + }, + { + "id": "components-statelabel-features--alert-fixed" + }, + { + "id": "components-statelabel-features--alert-dismissed" + }, { "id": "components-statelabel-features--small" }, @@ -56,9 +65,9 @@ }, { "name": "status", - "type": "'issueOpened' | 'issueClosed' | 'issueClosedNotPlanned' | 'pullOpened' | 'pullClosed' | 'pullMerged' | 'draft' | 'issueDraft' | 'unavailable' | 'open' | 'closed'", + "type": "'issueOpened' | 'issueClosed' | 'issueClosedNotPlanned' | 'pullOpened' | 'pullClosed' | 'pullMerged' | 'draft' | 'issueDraft' | 'pullQueued' | 'unavailable' | 'alertOpened' | 'alertFixed' | 'alertDismissed' | 'open' | 'closed'", "required": true } ], "subcomponents": [] -} \ No newline at end of file +} diff --git a/packages/react/src/StateLabel/StateLabel.features.stories.tsx b/packages/react/src/StateLabel/StateLabel.features.stories.tsx index cbee00a68b3..84728d747bc 100644 --- a/packages/react/src/StateLabel/StateLabel.features.stories.tsx +++ b/packages/react/src/StateLabel/StateLabel.features.stories.tsx @@ -19,6 +19,11 @@ export const PullMerged = () => Merged Queued export const Draft = () => Draft export const Unavailable = () => Unavailable + +export const AlertOpened = () => Open +export const AlertFixed = () => Fixed +export const AlertDismissed = () => Dismissed + export const Open = () => ( {/* Because open is a generic status, a visually hidden text could be added to specify the type of the artifact */} diff --git a/packages/react/src/StateLabel/StateLabel.module.css b/packages/react/src/StateLabel/StateLabel.module.css index 1f31d5f0aa2..ebeab6e2e2b 100644 --- a/packages/react/src/StateLabel/StateLabel.module.css +++ b/packages/react/src/StateLabel/StateLabel.module.css @@ -95,6 +95,25 @@ box-shadow: var(--boxShadow-thin, inset 0 0 0 1px) var(--borderColor-done-emphasis, transparent); } +.StateLabel:where([data-status='alertOpened']) { + background-color: var(--bgColor-open-emphasis); + color: var(--fgColor-onEmphasis); + box-shadow: var(--boxShadow-thin, inset 0 0 0 1px) var(--borderColor-open-emphasis, transparent); +} + +.StateLabel:where([data-status='alertFixed']) { + background-color: var(--bgColor-done-emphasis); + color: var(--fgColor-onEmphasis); + box-shadow: var(--boxShadow-thin, inset 0 0 0 1px) var(--borderColor-done-emphasis, transparent); +} + +.StateLabel:where([data-status='alertDismissed']) { + background-color: var(--bgColor-draft-emphasis, var(--bgColor-neutral-emphasis)); + color: var(--fgColor-onEmphasis); + box-shadow: var(--boxShadow-thin, inset 0 0 0 1px) + var(--borderColor-draft-emphasis, var(--borderColor-neutral-emphasis, transparent)); +} + .Icon { margin-right: var(--base-size-4); } diff --git a/packages/react/src/StateLabel/StateLabel.tsx b/packages/react/src/StateLabel/StateLabel.tsx index 15dc7735ead..f1357ae7ae8 100644 --- a/packages/react/src/StateLabel/StateLabel.tsx +++ b/packages/react/src/StateLabel/StateLabel.tsx @@ -9,6 +9,9 @@ import { IssueOpenedIcon, GitMergeQueueIcon, AlertIcon, + ShieldIcon, + ShieldCheckIcon, + ShieldSlashIcon, } from '@primer/octicons-react' import type React from 'react' import {forwardRef} from 'react' @@ -27,11 +30,14 @@ const octiconMap = { issueDraft: IssueDraftIcon, pullQueued: GitMergeQueueIcon, unavailable: AlertIcon, + alertOpened: ShieldIcon, + alertFixed: ShieldCheckIcon, + alertDismissed: ShieldSlashIcon, open: null, closed: null, } -const labelMap: Record = { +const labelMap: Record = { issueOpened: 'Issue', pullOpened: 'Pull request', issueClosed: 'Issue', @@ -42,6 +48,9 @@ const labelMap: Record { expect(HTMLRender().container).toMatchSnapshot() expect(HTMLRender().container).toMatchSnapshot() expect(HTMLRender().container).toMatchSnapshot() + expect(HTMLRender().container).toMatchSnapshot() + expect(HTMLRender().container).toMatchSnapshot() + expect(HTMLRender().container).toMatchSnapshot() }) it('respects the deprecated variant prop', () => { @@ -41,6 +44,10 @@ describe('StateLabel', () => { const screen2 = HTMLRender(Merged) expect(screen2.getByLabelText('Pull request')).toBeInTheDocument() // svg expect(screen2.getByText('Merged')).toBeInTheDocument() // text + + const screen3 = HTMLRender(Fixed) + expect(screen3.getByLabelText('Alert')).toBeInTheDocument() // svg + expect(screen3.getByText('Fixed')).toBeInTheDocument() // text }) it('renders open status without an icon', () => { const screen = HTMLRender(Open) diff --git a/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap b/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap index 0441aa1ba2d..1cc4928e48b 100644 --- a/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap +++ b/packages/react/src/StateLabel/__tests__/__snapshots__/StateLabel.test.tsx.snap @@ -335,3 +335,87 @@ exports[`StateLabel > respects the status prop 5`] = ` `; + +exports[`StateLabel > respects the status prop 6`] = ` +
+ + + + + +
+`; + +exports[`StateLabel > respects the status prop 7`] = ` +
+ + + + + +
+`; + +exports[`StateLabel > respects the status prop 8`] = ` +
+ + + + + +
+`;