Skip to content
Merged
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

This file was deleted.

44 changes: 30 additions & 14 deletions src/core/empty-data/__tests__/empty-data.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { render } from '@testing-library/react'
import { EmptyData } from '..'

describe('EmptyData', () => {
it('should render default component properly and match snapshot', () => {
const { asFragment } = render(
<EmptyData>
<EmptyData.Description>Description</EmptyData.Description>
<EmptyData.SecondaryDescription>Secondary Description</EmptyData.SecondaryDescription>
<EmptyData.ActionButton onClick={console.log}>Action Button Text</EmptyData.ActionButton>
</EmptyData>,
)
expect(asFragment()).toMatchSnapshot()
})
import { EmptyData } from '../empty-data'
import { render, screen } from '@testing-library/react'

test('renders as a div', () => {
const { container } = render(<EmptyData>Fake child</EmptyData>)
expect(container.firstElementChild?.tagName).toBe('DIV')
})

test('displays children', () => {
render(<EmptyData>Fake child</EmptyData>)
expect(screen.getByText('Fake child')).toBeVisible()
})

test('sets height via style prop when provided', () => {
render(<EmptyData height="--size-80">Fake child</EmptyData>)
expect(screen.getByText('Fake child')).toHaveAttribute('style', 'height: var(--size-80);')
})

test('preserves other inline styles when height is provided', () => {
render(
<EmptyData height="--size-40" style={{ color: 'red' }}>
Fake child
</EmptyData>,
)
expect(screen.getByText('Fake child')).toHaveAttribute('style', 'color: red; height: var(--size-40);')
})

test('forwards additional props to div', () => {
const { container } = render(<EmptyData data-testid="test-id">Fake child</EmptyData>)
expect(screen.getByTestId('test-id')).toBe(container.firstElementChild)
})
27 changes: 27 additions & 0 deletions src/core/empty-data/action/__tests__/action-button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EmptyDataActionButton } from '../action-button'
import { render, screen } from '@testing-library/react'

test('renders as a button element', () => {
render(<EmptyDataActionButton>Action</EmptyDataActionButton>)
expect(screen.getByRole('button', { name: 'Action' })).toBeVisible()
})

test('is a medium sized button', () => {
render(<EmptyDataActionButton>Action</EmptyDataActionButton>)
expect(screen.getByRole('button')).toHaveAttribute('data-size', 'medium')
})

test('is a tertiary button', () => {
render(<EmptyDataActionButton>Action</EmptyDataActionButton>)
expect(screen.getByRole('button')).toHaveAttribute('data-variant', 'tertiary')
})

test("uses the tertiary button's link styling", () => {
render(<EmptyDataActionButton>Action</EmptyDataActionButton>)
expect(screen.getByRole('button')).toHaveAttribute('data-use-link-style', 'true')
})

test('forwards additional props to the link', () => {
render(<EmptyDataActionButton data-testid="test-id">Action</EmptyDataActionButton>)
expect(screen.getByTestId('test-id')).toBe(screen.getByRole('button'))
})
31 changes: 31 additions & 0 deletions src/core/empty-data/action/__tests__/action.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { EmptyDataAction } from '../action'
import { render, screen } from '@testing-library/react'

test('renders as a link element', () => {
render(<EmptyDataAction href="https://fake.url">Action</EmptyDataAction>)
expect(screen.getByRole('link', { name: 'Action' })).toBeVisible()
})

test('is a medium sized button', () => {
render(<EmptyDataAction href="https://fake.url">Action</EmptyDataAction>)
expect(screen.getByRole('link')).toHaveAttribute('data-size', 'medium')
})

test('is a tertiary button', () => {
render(<EmptyDataAction href="https://fake.url">Action</EmptyDataAction>)
expect(screen.getByRole('link')).toHaveAttribute('data-variant', 'tertiary')
})

test("uses the tertiary button's link styling", () => {
render(<EmptyDataAction href="https://fake.url">Action</EmptyDataAction>)
expect(screen.getByRole('link')).toHaveAttribute('data-use-link-style', 'true')
})

test('forwards additional props to the link', () => {
render(
<EmptyDataAction data-testid="test-id" href="https://fake.url">
Action
</EmptyDataAction>,
)
expect(screen.getByTestId('test-id')).toBe(screen.getByRole('link'))
})
15 changes: 15 additions & 0 deletions src/core/empty-data/action/action-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Button } from '#src/core/button'

import type { AttributesToOmit } from './common'
import type { ComponentProps, MouseEventHandler, ReactNode } from 'react'

interface EmptyDataActionButtonProps extends Omit<ComponentProps<typeof Button>, AttributesToOmit> {
/** The action's label. */
children: ReactNode
/** The action to perform. */
onClick?: MouseEventHandler<HTMLButtonElement>
}

export function EmptyDataActionButton(props: EmptyDataActionButtonProps) {
return <Button {...props} size="medium" variant="tertiary" useLinkStyle />
}
46 changes: 46 additions & 0 deletions src/core/empty-data/action/action.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { EmptyDataAction } from './action'
import { EmptyDataActionButton } from './action-button'

import type { Meta, StoryObj } from '@storybook/react-vite'

const meta = {
title: 'Core/EmptyData/Action',
component: EmptyDataAction,
subcomponents: {
EmptyDataActionButton: EmptyDataActionButton,
},
argTypes: {
children: {
control: 'text',
},
href: {
control: false,
},
},
} satisfies Meta<typeof EmptyDataAction>

export default meta
type Story = StoryObj<typeof EmptyDataAction>

/**
* In most cases, the action will navigate users to a page or drawer that allows them to create
* the kind of entity for which the `EmptyData` component is currently communicating that none exist.
* This is why the standard action is an `<a>` element.
*/
export const Example: Story = {
args: {
children: 'No things found',
href: globalThis.top?.location.href!,
},
}

/**
* The `EmptyData.ActionButton` is a `<button>`-based version of `EmptyData.Action`. It can be used
* in scenarios where the action needs to occur on click rather than a simple navigation.
*/
export const Button: StoryObj<typeof EmptyDataActionButton> = {
args: {
children: 'No things found',
},
render: (args) => <EmptyDataActionButton {...args} />,
}
22 changes: 22 additions & 0 deletions src/core/empty-data/action/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AnchorButton } from '#src/core/button'

import type { AttributesToOmit } from './common'
import type { ComponentProps, ReactNode } from 'react'

interface EmptyDataActionProps extends Omit<ComponentProps<typeof AnchorButton>, AttributesToOmit> {
/** The action's label. */
children: ReactNode
/** The URL to navigate to; will typically be an entity creation page or drawer. */
href: string
}

/**
* A simple action component. Comes in two varieties: `EmptyData.Action`, which renders as an
* anchor element, and `EmptyData.ActionButton`, which renders as an anchor element.
*
* Use `EmptyData.Action` when you need button-like styling but want to navigate to a URL. Use
* `EmptyData.ActionButton` when the action needs to occur on click.
*/
export function EmptyDataAction(props: EmptyDataActionProps) {
return <AnchorButton {...props} size="medium" variant="tertiary" useLinkStyle />
}
5 changes: 5 additions & 0 deletions src/core/empty-data/action/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// NOTE: We omit...
// - size, because our empty data actions are always medium in size
// - variant, because our empty data actions are always tertiary buttons
// - useLinkStyle, because our empty data actions should always use the tertiary button's link styling
export type AttributesToOmit = 'size' | 'variant' | 'useLinkStyle'
2 changes: 2 additions & 0 deletions src/core/empty-data/action/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './action'
export * from './action-button'
18 changes: 18 additions & 0 deletions src/core/empty-data/description/__tests__/description.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EmptyDataDescription } from '../description'
import { render, screen } from '@testing-library/react'

test('displays the main text in a heading level 3 element', () => {
render(<EmptyDataDescription>No things found</EmptyDataDescription>)
expect(screen.getByRole('heading', { name: 'No things found', level: 3 })).toBeVisible()
})

test('displays the secondary text in a paragraph element', () => {
render(<EmptyDataDescription secondaryText="Secondary text">No things found</EmptyDataDescription>)
expect(screen.getByRole('paragraph')).toBeVisible()
expect(screen.getByRole('paragraph')).toHaveTextContent('Secondary text')
})

test('forwards additional props to the root element', () => {
const { container } = render(<EmptyDataDescription data-testid="test-id">No things found</EmptyDataDescription>)
expect(screen.getByTestId('test-id')).toBe(container.firstElementChild)
})
29 changes: 29 additions & 0 deletions src/core/empty-data/description/description.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { EmptyDataDescription } from './description'

import type { Meta, StoryObj } from '@storybook/react-vite'

const meta = {
title: 'Core/EmptyData/Description',
component: EmptyDataDescription,
argTypes: {
children: {
control: 'text',
},
secondaryText: {
control: 'text',
},
},
} satisfies Meta<typeof EmptyDataDescription>

export default meta
type Story = StoryObj<typeof EmptyDataDescription>

/**
*
*/
export const Example: Story = {
args: {
children: 'No things found',
secondaryText: 'Secondary text',
},
}
22 changes: 22 additions & 0 deletions src/core/empty-data/description/description.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ElEmptyDataDescription, ElEmptyDataDescriptionSecondaryText, ElEmptyDataDescriptionTitle } from './styles'

import type { HTMLAttributes, ReactNode } from 'react'

interface EmptyDataDescriptionProps extends HTMLAttributes<HTMLDivElement> {
/** The empty data's title text. */
children: ReactNode
/** The empty data's secondary text. */
secondaryText?: ReactNode
}

/**
* A simple component that displays a title and optional secondary text for the `EmptyData`.
*/
export function EmptyDataDescription({ children, secondaryText, ...rest }: EmptyDataDescriptionProps) {
return (
<ElEmptyDataDescription {...rest}>
<ElEmptyDataDescriptionTitle>{children}</ElEmptyDataDescriptionTitle>
{secondaryText && <ElEmptyDataDescriptionSecondaryText>{secondaryText}</ElEmptyDataDescriptionSecondaryText>}
</ElEmptyDataDescription>
)
}
2 changes: 2 additions & 0 deletions src/core/empty-data/description/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './description'
export * from './styles'
27 changes: 27 additions & 0 deletions src/core/empty-data/description/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { font } from '#src/core/text'
import { styled } from '@linaria/react'

export const ElEmptyDataDescription = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

text-align: center;
`

export const ElEmptyDataDescriptionTitle = styled.h3`
color: var(--text-primary);
${font('base', 'regular')}

margin: 0;
padding: 0;
`

export const ElEmptyDataDescriptionSecondaryText = styled.p`
color: var(--text-secondary);
${font('sm', 'regular')}

margin: 0;
padding: 0;
`
32 changes: 0 additions & 32 deletions src/core/empty-data/empty-data.mdx

This file was deleted.

Loading