Skip to content

Commit

Permalink
new card variant (#578)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sodik authored Nov 19, 2024
1 parent 346d699 commit 7bd4b2d
Show file tree
Hide file tree
Showing 30 changed files with 160 additions and 92 deletions.
2 changes: 1 addition & 1 deletion jest.config.base.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
roots: ['<rootDir>'],
testEnvironment: 'jsdom',
transformIgnorePatterns: ['node_modules/(?!@hazelcast)'],
transformIgnorePatterns: ['node_modules/(?!(\\.pnpm|@hazelcast))'],
transform: {
'\\.[jt]sx?$': 'babel-jest',
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion packages/ui/__stories__/Card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export default {
} as Meta<CardProps>

const Template: Story<CardProps> = (args) => {
return <Card {...args} />
return (
<>
<Card {...args} />
<Card variant="bordered" {...args} />
</>
)
}

export const Simple = Template.bind({})
Expand Down
131 changes: 65 additions & 66 deletions packages/ui/__tests__/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import React from 'react'
import React, { ReactElement } from 'react'
import { Database } from 'react-feather'
import { mountAndCheckA11Y } from '@hazelcast/test-helpers'
import { DataTestProp } from '@hazelcast/helpers'
import { axe, toHaveNoViolations } from 'jest-axe'
import { render, screen, within } from '@testing-library/react'

import { Card, IconButton } from '../src'

import styles from '../src/Card.module.scss'
import { IconProps } from '../src/Icon'
import { IconButtonProps } from '../src/IconButton'

expect.extend(toHaveNoViolations)
const renderAndCheckA11Y = async (node: ReactElement) => {
const result = render(node)

const results = await axe(result.container)
expect(results).toHaveNoViolations()

return result
}

describe('Card', () => {
const cardHeadingIcon = Database
Expand All @@ -16,77 +24,68 @@ describe('Card', () => {
const cardContent = 'Card content'

it('renders title and content', async () => {
const wrapper = await mountAndCheckA11Y(<Card title={cardHeadingTitle}>{cardContent}</Card>)

const card = wrapper.findDataTest('card-wrapper')
const title = card.findDataTest('card-heading').findDataTest('card-heading-title')
const content = card.findDataTest('card-content')

expect(title.props()).toEqual({
'data-test': 'card-heading-title',
className: styles.title,
children: cardHeadingTitle,
})

expect(content.props()).toEqual({
'data-test': 'card-content',
className: styles.content,
children: cardContent,
})
await renderAndCheckA11Y(<Card title={cardHeadingTitle}>{cardContent}</Card>)

expect(screen.getByTestId('card-wrapper')).toBeInTheDocument()
expect(screen.getByTestId('card-heading')).toBeInTheDocument()

const cardTitle = screen.getByTestId('card-heading-title')
expect(cardTitle).toBeInTheDocument()
expect(cardTitle.tagName).toEqual('H2')
expect(cardTitle.className).toEqual(styles.title)
expect(within(cardTitle).queryByText(cardHeadingTitle))

const content = screen.getByTestId('card-content')
expect(content.className).toEqual(styles.content)
expect(within(content).queryByText(cardContent)).toBeInTheDocument()
})

it('renders heading with icon, title and additiona content, also renders separator and card content', async () => {
const wrapper = await mountAndCheckA11Y(
it('renders heading with icon, title and additional content, also renders separator and card content', async () => {
await renderAndCheckA11Y(
<Card headingIcon={cardHeadingIcon} title={cardHeadingTitle} headingContent={cardHeadingContent} separator>
{cardContent}
</Card>,
)

const card = wrapper.findDataTest('card-wrapper')
const heading = card.findDataTest('card-heading')
const headingIcon = heading.findDataTest('card-heading-icon')
const headingTitle = heading.findDataTest('card-heading-title')
const headingContent = heading.childAt(2)
const separator = card.findDataTest('card-separator')
const content = card.findDataTest('card-content')

expect(headingIcon.props()).toEqual<IconProps & DataTestProp>({
'data-test': 'card-heading-icon',
className: styles.icon,
ariaHidden: true,
icon: cardHeadingIcon,
})

expect(headingTitle.props()).toEqual({
'data-test': 'card-heading-title',
className: `${styles.title} ${styles.space}`,
children: cardHeadingTitle,
})

expect(separator.props()).toEqual({
'data-test': 'card-separator',
className: styles.separator,
})

expect(headingContent.type()).toEqual(IconButton)
expect(headingContent.props()).toEqual<IconButtonProps>({
kind: 'primary',
ariaLabel: 'Check out the Database',
icon: Database,
component: 'a',
href: '#',
})

expect(content.props()).toEqual({
'data-test': 'card-content',
className: styles.content,
children: cardContent,
})
const title = screen.getByTestId('card-heading-title')
expect(title).toBeInTheDocument()
expect(title.className).toEqual(`${styles.title} ${styles.space}`)
expect(within(title).queryByText(cardHeadingTitle)).toBeInTheDocument()

expect(screen.queryByTestId('card-separator')).toBeInTheDocument()
expect(screen.getByTestId('card-separator').className).toEqual(styles.separator)

const content = screen.getByTestId('card-content')
expect(content).toBeInTheDocument()
expect(content.className).toEqual(styles.content)
expect(within(content).queryByText(cardContent)).toBeInTheDocument()

expect(screen.queryByTestId('card-heading-icon')).not.toBeInTheDocument()
})

it('renders caption', async () => {
const wrapper = await mountAndCheckA11Y(<Card caption={<span id="caption">Caption</span>}>{cardContent}</Card>)
await renderAndCheckA11Y(<Card caption={<span id="caption">Caption</span>}>{cardContent}</Card>)

expect(screen.queryByText('Caption')).toBeInTheDocument()
})

it('renders bordered variant', async () => {
await renderAndCheckA11Y(
<Card variant="bordered" title={cardHeadingTitle}>
{cardContent}
</Card>,
)

expect(screen.getByTestId('card-wrapper').className.includes(styles.bordered)).toBeTruthy()
})

it('renders custom title tag', async () => {
await renderAndCheckA11Y(
<Card titleTagName="h6" title={cardHeadingTitle}>
{cardContent}
</Card>,
)

expect(wrapper.find('#caption').exists()).toBeTruthy()
expect(screen.getByTestId('card-heading-title').tagName).toEqual('H6')
})
})
6 changes: 2 additions & 4 deletions packages/ui/__tests__/Carousel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React from 'react'
import { mountAndCheckA11Y } from '@hazelcast/test-helpers'
import { act } from 'react-dom/test-utils'
import { mountAndCheckA11Y, axeDefaultOptions } from '@hazelcast/test-helpers'

import { Card, Carousel } from '../src'

import { axeDefaultOptions } from '@hazelcast/test-helpers/src'
import { act } from 'react-dom/test-utils'

describe('Carousel', () => {
const contentCarousel = [<Card key={1}>Item1</Card>, <Card key={2}>Item2</Card>, <Card key={3}>Item3</Card>]

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"use-persisted-state": "^0.3.3"
},
"devDependencies": {
"@hazelcast/test-helpers": "^1.3.2",
"@hazelcast/test-helpers": "1.3.3-next.20",
"@storybook/addon-docs": "^6.5.16",
"@storybook/addons": "^6.5.16",
"@storybook/builder-webpack5": "^6.5.16",
Expand Down
52 changes: 42 additions & 10 deletions packages/ui/src/Card.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,19 @@
.wrapper {
$padding: c.$grid * 5;

padding: $padding;
border-radius: c.$borderRadius;
background: c.$colorNeutralWhite;
border: c.$borderWidth solid c.$colorNeutral;

.heading {
@include c.typographyH3;

display: flex;
align-items: center;
margin-bottom: c.$grid * 5;

.icon {
margin-right: c.$grid * 2.5;
}

.title {
margin: 0;
font-size: c.$fontSizeBodyNormal !important;
line-height: c.$lineHeightBodyNormal !important;

&.space {
margin-right: c.$grid * 2.5;
Expand All @@ -37,9 +31,8 @@
}

.content {
padding-top: c.$grid * 6;
@include c.typographyBodyNormal;

margin-top: c.$grid * 4;
}

&.noTitle {
Expand All @@ -50,10 +43,49 @@

.caption {
display: flex;
margin: (-$padding) (-$padding) $padding;
margin-bottom: $padding;

& > * {
flex: 1;
}
}

&.bordered {
padding: $padding;
border-radius: c.$borderRadius;
border: c.$borderWidth solid c.$colorNeutral;

.heading {
@include c.typographyH3;

display: flex;
align-items: center;
margin: 0;

.icon {
margin-right: c.$grid * 2.5;
}

.title {
margin: 0;
font-size: c.$fontSizeBodyNormal !important;
line-height: c.$lineHeightBodyNormal !important;

&.space {
margin-right: c.$grid * 2.5;
}
}
}

.content {
@include c.typographyBodyNormal;

margin-top: c.$grid * 4;
padding: 0;
}

.caption {
margin: (-$padding) (-$padding) $padding;
}
}
}
22 changes: 18 additions & 4 deletions packages/ui/src/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, ReactNode } from 'react'
import React, { ElementType, FC, ReactNode } from 'react'
import cn from 'classnames'
import { DataTestProp } from '@hazelcast/helpers'

Expand All @@ -10,8 +10,10 @@ export type CardProps = {
headingIcon?: IconProps['icon']
caption?: ReactNode
title?: string
titleTagName?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
headingContent?: ReactNode
separator?: boolean
variant?: 'bordered' | 'default'
children: ReactNode
className?: string
iconClassName?: string
Expand All @@ -20,6 +22,13 @@ export type CardProps = {
headerClassName?: string
} & DataTestProp

interface TitleProps {
as: ElementType
className?: string
children?: ReactNode
}
const Title: FC<TitleProps> = ({ as: Tag, ...props }) => <Tag data-test="card-heading-title" {...props} />

/**
* ### Purpose
* Card components define content hierarchy (visual and logical), separating content into "groups".
Expand All @@ -32,24 +41,29 @@ export const Card: FC<CardProps> = ({
title,
headingContent,
separator = false,
variant = 'default',
'data-test': dataTest,
children,
titleTagName = 'h2',
className,
iconClassName,
titleClassName,
contentClassName,
headerClassName,
caption,
}) => (
<div data-test={dataTest ?? 'card-wrapper'} className={cn(styles.wrapper, { [styles.noTitle]: !title }, className)}>
<div
data-test={dataTest ?? 'card-wrapper'}
className={cn(styles.wrapper, { [styles.noTitle]: !title, [styles.bordered]: variant === 'bordered' }, className)}
>
{caption && <div className={styles.caption}>{caption}</div>}
{(title || headingContent || headingIcon) && (
<div data-test="card-heading" className={cn(styles.heading, headerClassName)}>
{headingIcon && <Icon data-test="card-heading-icon" icon={headingIcon} className={cn(styles.icon, iconClassName)} ariaHidden />}
{title && (
<h3 data-test="card-heading-title" className={cn(styles.title, { [styles.space]: !!headingContent }, titleClassName)}>
<Title as={titleTagName} className={cn(styles.title, { [styles.space]: !!headingContent }, titleClassName)}>
{title}
</h3>
</Title>
)}
{headingContent}
</div>
Expand Down
Loading

0 comments on commit 7bd4b2d

Please sign in to comment.