Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blankslate-ga-ready.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Blankslate: Promote component to the main `@primer/react` entrypoint and add polymorphic action exports
61 changes: 49 additions & 12 deletions packages/react/src/Blankslate/Blankslate.docs.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
{
"id": "blankslate",
"name": "Blankslate",
"status": "draft",
"status": "alpha",
"a11yReviewed": "2025-01-08",
"stories": [
{
"id": "experimental-components-blankslate--default"
"id": "components-blankslate--default"
},
{
"id": "experimental-components-blankslate-features--with-visual"
"id": "components-blankslate-features--with-visual"
},
{
"id": "experimental-components-blankslate-features--with-primary-action-as-link"
"id": "components-blankslate-features--with-action-as-link"
},
{
"id": "experimental-components-blankslate-features--with-primary-action-as-button"
"id": "components-blankslate-features--with-action-as-button"
},
{
"id": "experimental-components-blankslate-features--with-secondary-action"
"id": "components-blankslate-features--with-secondary-action"
},
{
"id": "experimental-components-blankslate-features--with-border"
"id": "components-blankslate-features--with-border"
},
{
"id": "experimental-components-blankslate-features--narrow"
"id": "components-blankslate-features--narrow"
},
{
"id": "experimental-components-blankslate-features--spacious"
"id": "components-blankslate-features--spacious"
}
],
"importPath": "@primer/react/experimental",
"importPath": "@primer/react",
"props": [
{
"name": "border",
Expand All @@ -55,7 +55,7 @@
{
"name": "size",
"type": "'small' | 'medium' | 'large'",
"description": "The size of the componeont",
"description": "The size of the component",
"defaultValue": "'medium'"
}
],
Expand All @@ -77,9 +77,39 @@
"name": "Blankslate.Description",
"props": []
},
{
"name": "Blankslate.Action",
"props": [
{
"name": "as",
"type": "'button' | 'a'",
"required": false,
"description": "The element to render for the action."
},
{
"name": "href",
"type": "string",
"required": false,
"description": "Link to complete the action. If defined, the action will render as an anchor."
},
{
"name": "variant",
"type": "'primary' | 'secondary'",
"required": false,
"defaultValue": "'primary'",
"description": "The visual treatment for the action."
}
]
},
{
"name": "Blankslate.PrimaryAction",
"props": [
{
"name": "as",
"type": "'button' | 'a'",
"required": false,
"description": "The element to render for the action."
},
{
"name": "href",
"type": "string",
Expand All @@ -91,10 +121,17 @@
{
"name": "Blankslate.SecondaryAction",
"props": [
{
"name": "as",
"type": "'button' | 'a'",
"required": false,
"description": "The element to render for the action."
},
{
"name": "href",
"type": "string",
"description": "Link to complete secondary action"
"required": false,
"description": "Link to complete secondary action. If omitted, the action will render as a button."
}
]
}
Expand Down
27 changes: 17 additions & 10 deletions packages/react/src/Blankslate/Blankslate.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import {Blankslate} from '../Blankslate'
import {ConfirmationDialog} from '../ConfirmationDialog/ConfirmationDialog'

export default {
title: 'Experimental/Components/Blankslate/Features',
title: 'Components/Blankslate/Features',
component: Blankslate,
subcomponents: {
'Blankslate.Visual': Blankslate.Visual,
'Blankslate.Heading': Blankslate.Heading,
'Blankslate.Description': Blankslate.Description,
'Blankslate.Action': Blankslate.Action,
'Blankslate.PrimaryAction': Blankslate.PrimaryAction,
'Blankslate.SecondaryAction': Blankslate.SecondaryAction,
},
Expand All @@ -25,18 +26,18 @@ export const WithVisual = () => (
</Blankslate>
)

export const WithPrimaryActionAsLink = () => (
export const WithActionAsLink = () => (
<Blankslate>
<Blankslate.Visual>
<BookIcon size="medium" />
</Blankslate.Visual>
<Blankslate.Heading>Blankslate heading</Blankslate.Heading>
<Blankslate.Description>Use it to provide information when no dynamic content exists.</Blankslate.Description>
<Blankslate.PrimaryAction href="#">Primary action</Blankslate.PrimaryAction>
<Blankslate.Action href="#">Primary action</Blankslate.Action>
</Blankslate>
)

export const WithPrimaryActionAsButton = () => {
export const WithActionAsButton = () => {
const [isOpen, setIsOpen] = React.useState(false)
const onDialogClose = React.useCallback(() => setIsOpen(false), [])

Expand All @@ -48,7 +49,7 @@ export const WithPrimaryActionAsButton = () => {
</Blankslate.Visual>
<Blankslate.Heading>Blankslate heading</Blankslate.Heading>
<Blankslate.Description>Use it to provide information when no dynamic content exists.</Blankslate.Description>
<Blankslate.PrimaryAction onClick={() => setIsOpen(true)}>Primary action</Blankslate.PrimaryAction>
<Blankslate.Action onClick={() => setIsOpen(true)}>Primary action</Blankslate.Action>
</Blankslate>
{isOpen ? (
<ConfirmationDialog
Expand All @@ -72,7 +73,9 @@ export const WithSecondaryAction = () => (
</Blankslate.Visual>
<Blankslate.Heading>Blankslate heading</Blankslate.Heading>
<Blankslate.Description>Use it to provide information when no dynamic content exists.</Blankslate.Description>
<Blankslate.SecondaryAction href="#">Secondary action</Blankslate.SecondaryAction>
<Blankslate.Action href="#" variant="secondary">
Secondary action
</Blankslate.Action>
</Blankslate>
)

Expand Down Expand Up @@ -113,8 +116,10 @@ export const SizeSmall = () => (
</Blankslate.Visual>
<Blankslate.Heading>Blankslate heading</Blankslate.Heading>
<Blankslate.Description>Use it to provide information when no dynamic content exists.</Blankslate.Description>
<Blankslate.PrimaryAction href="#">Primary action</Blankslate.PrimaryAction>
<Blankslate.SecondaryAction href="#">Secondary action</Blankslate.SecondaryAction>
<Blankslate.Action href="#">Primary action</Blankslate.Action>
<Blankslate.Action href="#" variant="secondary">
Secondary action
</Blankslate.Action>
</Blankslate>
)

Expand All @@ -125,7 +130,9 @@ export const SizeLarge = () => (
</Blankslate.Visual>
<Blankslate.Heading>Blankslate heading</Blankslate.Heading>
<Blankslate.Description>Use it to provide information when no dynamic content exists.</Blankslate.Description>
<Blankslate.PrimaryAction href="#">Primary action</Blankslate.PrimaryAction>
<Blankslate.SecondaryAction href="#">Secondary action</Blankslate.SecondaryAction>
<Blankslate.Action href="#">Primary action</Blankslate.Action>
<Blankslate.Action href="#" variant="secondary">
Secondary action
</Blankslate.Action>
</Blankslate>
)
17 changes: 12 additions & 5 deletions packages/react/src/Blankslate/Blankslate.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import {Blankslate} from '../Blankslate'
import type {ComponentProps} from '../utils/types'

export default {
title: 'Experimental/Components/Blankslate',
title: 'Components/Blankslate',
component: Blankslate,
subcomponents: {
'Blankslate.Visual': Blankslate.Visual,
'Blankslate.Heading': Blankslate.Heading,
'Blankslate.Description': Blankslate.Description,
'Blankslate.Action': Blankslate.Action,
'Blankslate.PrimaryAction': Blankslate.PrimaryAction,
'Blankslate.SecondaryAction': Blankslate.SecondaryAction,
},
Expand All @@ -25,8 +26,10 @@ export const Default = () => (
Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and
document software better, together.
</Blankslate.Description>
<Blankslate.PrimaryAction href="#">Create the first page</Blankslate.PrimaryAction>
<Blankslate.SecondaryAction href="#">Learn more about wikis</Blankslate.SecondaryAction>
<Blankslate.Action href="#">Create the first page</Blankslate.Action>
<Blankslate.Action href="#" variant="secondary">
Learn more about wikis
</Blankslate.Action>
</Blankslate>
)

Expand All @@ -42,8 +45,12 @@ export const Playground: StoryFn<
Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and
document software better, together.
</Blankslate.Description>
{primaryAction ? <Blankslate.PrimaryAction href="#">Create the first page</Blankslate.PrimaryAction> : null}
{secondaryAction ? <Blankslate.SecondaryAction href="#">Learn more about wikis</Blankslate.SecondaryAction> : null}
{primaryAction ? <Blankslate.Action href="#">Create the first page</Blankslate.Action> : null}
{secondaryAction ? (
<Blankslate.Action href="#" variant="secondary">
Learn more about wikis
</Blankslate.Action>
) : null}
</Blankslate>
)

Expand Down
93 changes: 93 additions & 0 deletions packages/react/src/Blankslate/Blankslate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('Blankslate', () => {
</Blankslate.Visual>
<Blankslate.Heading>Test Heading</Blankslate.Heading>
<Blankslate.Description>Test description</Blankslate.Description>
<Blankslate.Action>Action</Blankslate.Action>
<Blankslate.PrimaryAction>Primary action</Blankslate.PrimaryAction>
<Blankslate.SecondaryAction href="https://example.com">Secondary action</Blankslate.SecondaryAction>
</Blankslate>,
Expand All @@ -47,6 +48,9 @@ describe('Blankslate', () => {
expect(
container.querySelector('[data-component="Blankslate"] [data-component="Blankslate.Description"]'),
).toBeInTheDocument()
expect(
container.querySelector('[data-component="Blankslate"] [data-component="Blankslate.Action"]'),
).toBeInTheDocument()
expect(
container.querySelector('[data-component="Blankslate"] [data-component="Blankslate.PrimaryAction"]'),
).toBeInTheDocument()
Expand Down Expand Up @@ -100,6 +104,80 @@ describe('Blankslate', () => {
})
})

describe('Blankslate.Action', () => {
it('should render a primary action button by default', () => {
render(
<Blankslate>
<Blankslate.Action>Action</Blankslate.Action>
</Blankslate>,
)
expect(screen.getByRole('button', {name: 'Action'})).toBeInTheDocument()
expect(screen.getByRole('button', {name: 'Action'})).toHaveAttribute('data-variant', 'primary')
})

it('should handle click events on the button', async () => {
const user = userEvent.setup()
const onClick = vi.fn()
render(
<Blankslate>
<Blankslate.Action onClick={onClick}>Action</Blankslate.Action>
</Blankslate>,
)

await user.click(screen.getByRole('button', {name: 'Action'}))
expect(onClick).toHaveBeenCalledTimes(1)
})

it('should render as an anchor when href is provided', () => {
render(
<Blankslate>
<Blankslate.Action href="https://example.com">Action</Blankslate.Action>
</Blankslate>,
)
const link = screen.getByRole('link', {name: 'Action'})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', 'https://example.com')
})

it('should render secondary actions as links by default', () => {
render(
<Blankslate>
<Blankslate.Action href="https://example.com" variant="secondary">
Action
</Blankslate.Action>
</Blankslate>,
)
const link = screen.getByRole('link', {name: 'Action'})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', 'https://example.com')
expect(link).toHaveAttribute('data-component', 'Link')
})

it('should render secondary actions as buttons', async () => {
const user = userEvent.setup()
const onClick = vi.fn()
render(
<Blankslate>
<Blankslate.Action as="button" onClick={onClick} variant="secondary">
Action
</Blankslate.Action>
</Blankslate>,
)

await user.click(screen.getByRole('button', {name: 'Action'}))
expect(onClick).toHaveBeenCalledTimes(1)
})

it('should render small primary actions when the Blankslate is small', () => {
render(
<Blankslate size="small">
<Blankslate.Action>Action</Blankslate.Action>
</Blankslate>,
)
expect(screen.getByRole('button', {name: 'Action'})).toHaveAttribute('data-size', 'small')
})
})

describe('Blankslate.PrimaryAction', () => {
it('should render a primary action button', () => {
render(
Expand Down Expand Up @@ -146,5 +224,20 @@ describe('Blankslate', () => {
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', 'https://example.com')
})

it('should render a secondary action button', async () => {
const user = userEvent.setup()
const onClick = vi.fn()
render(
<Blankslate>
<Blankslate.SecondaryAction as="button" onClick={onClick}>
Secondary action
</Blankslate.SecondaryAction>
</Blankslate>,
)

await user.click(screen.getByRole('button', {name: 'Secondary action'}))
expect(onClick).toHaveBeenCalledTimes(1)
})
})
})
Loading
Loading