Skip to content

Commit

Permalink
feat(island-ui): image/logo option for CategoryCard (#6125)
Browse files Browse the repository at this point in the history
* Add image option for CategoryCard

* Change hasImage check

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
stjanilofts and kodiakhq[bot] authored Jan 7, 2022
1 parent 66cf7bc commit eb719d2
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 57 deletions.
21 changes: 21 additions & 0 deletions libs/island-ui/core/src/lib/CategoryCard/CategoryCard.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { style } from '@vanilla-extract/css'
import { themeUtils } from '@island.is/island-ui/theme'

export const imageContainer = style({
position: 'relative',
maxWidth: '20%',
width: '100%',
})

export const imageContainerStacked = style({
maxWidth: '50%',
})

export const imageContainerHidden = style({
display: 'none',
...themeUtils.responsiveStyle({
sm: {
display: 'flex',
},
}),
})
74 changes: 71 additions & 3 deletions libs/island-ui/core/src/lib/CategoryCard/CategoryCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { withFigma } from '../../utils/withFigma'
import { GridColumn } from '../Grid/GridColumn/GridColumn'
import { GridContainer } from '../Grid/GridContainer/GridContainer'
import { GridRow } from '../Grid/GridRow/GridRow'
import { CategoryCard } from './CategoryCard'
import { CategoryCard, CategoryCardImage, STACK_WIDTH } from './CategoryCard'

export default {
title: 'Cards/CategoryCard',
Expand All @@ -30,7 +30,7 @@ const getDemoTags = (amount: number) => {
const getRandomTag = () =>
demoTags[Math.floor(Math.random() * demoTags.length)]

return Array.from({ length: amount }, (_, i) => ({
return Array.from({ length: amount }, () => ({
label: getRandomTag(),
href: '/',
}))
Expand Down Expand Up @@ -156,7 +156,7 @@ export const OnlyLabelTag = () => (
<CategoryCard
heading="Atvinnurekstur og sjálfstætt starfandi"
text="Stofnun fyrirtækja, launagreiðslur, gjaldþrot, löggildingar, starfsleyfi, vinnuvernd og fleira"
tags={[{ label: 'Not clickable' }]}
tags={[{ label: 'Not clickable', disabled: true }]}
/>
)

Expand All @@ -168,3 +168,71 @@ export const TruncatedHeading = () => (
tags={getDemoTags(4)}
/>
)

export const HyphenatedHeading = () => (
<GridContainer>
<GridRow>
<GridColumn span={['12/12', '12/12', '6/12']} paddingBottom={3}>
<CategoryCard
heading="Vaðlaheiðarvegavinnuverkfærageymsluskúraútidyralyklakippuhringur"
text="Meðal annars fæðingarorlof, nöfn, forsjá, gifting og skilnaður."
hyphenate
/>
</GridColumn>
<GridColumn span={['12/12', '12/12', '6/12']} paddingBottom={3}>
<CategoryCard
heading="Vaðlaheiðarvegavinnuverkfærageymsluskúraútidyralyklakippuhringur"
text="Meðal annars fæðingarorlof, nöfn, forsjá, gifting og skilnaður."
hyphenate
/>
</GridColumn>
</GridRow>
</GridContainer>
)

const imageProps: CategoryCardImage = {
alt: 'Logo',
objectFit: 'contain',
src:
'data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iNDAiCiAgaGVpZ2h0PSI0MCIKICBmaWxsPSJub25lIgogIHZpZXdCb3g9IjAgMCA0MCA0MCIKICBjbGFzcz0iTG9nb19yb290X18xZXNhcWRzMCIKICBzdHlsZT0iY29sb3I6ICNmZmYiCiAgYXJpYS1sYWJlbD0iaXNsYW5kLmlzIGxvZ28iCj4KICA8cGF0aAogICAgZmlsbD0idXJsKCNwYWludDBfbGluZWFyX2Zvb3Rlcl9sb2dvKSIKICAgIGQ9Ik04LjIyMjU0IDE2LjIzMzJWMzkuMDQ1OEM4LjIyMjU0IDM5LjU3NjMgNy45NTcyOSAzOS44NDE1IDcuNDI2ODEgMzkuODQxNUgyLjAxNTE4QzEuNDg0NjkgMzkuODQxNSAxLjIxOTQ1IDM5LjU3NjMgMS4yMTk0NSAzOS4wNDU4VjE2LjIzMzJDMS4yMTk0NSAxNS43MDI3IDEuNDg0NjkgMTUuNDM3NSAyLjAxNTE4IDE1LjQzNzVINy40MjY4MUM3Ljk1NzI5IDE1LjQzNzUgOC4yMjI1NCAxNS43MDI3IDguMjIyNTQgMTYuMjMzMlpNMjAgMzAuNTU4QzE3LjM0OTIgMzAuNTU4IDE1LjI3OSAzMi42Mjk5IDE1LjI3OSAzNS4yNzlDMTUuMjc5IDM3LjkyOTggMTcuMzUwOSA0MCAyMCA0MEMyMi42NTA4IDQwIDI0LjcyMSAzNy45MjgxIDI0LjcyMSAzNS4yNzlDMjQuNzIxIDMyLjYyOTkgMjIuNjUwOCAzMC41NTggMjAgMzAuNTU4Wk0zNS4yNzkgMzAuNTU4QzMyLjYyODIgMzAuNTU4IDMwLjU1OCAzMi42Mjk5IDMwLjU1OCAzNS4yNzlDMzAuNTU4IDM3LjkyOTggMzIuNjI5OSA0MCAzNS4yNzkgNDBDMzcuOTI5OCA0MCA0MCAzNy45MjgxIDQwIDM1LjI3OUM0MCAzMi42Mjk5IDM3LjkyODEgMzAuNTU4IDM1LjI3OSAzMC41NThaTTIwIDE1LjI3OUMxNy4zNDkyIDE1LjI3OSAxNS4yNzkgMTcuMzUwOSAxNS4yNzkgMjBDMTUuMjc5IDIyLjY1MDggMTcuMzUwOSAyNC43MjEgMjAgMjQuNzIxQzIyLjY1MDggMjQuNzIxIDI0LjcyMSAyMi42NDkxIDI0LjcyMSAyMEMyNC43MjEgMTcuMzUwOSAyMi42NTA4IDE1LjI3OSAyMCAxNS4yNzlaTTM1LjI3OSAxNS4yNzlDMzIuNjI4MiAxNS4yNzkgMzAuNTU4IDE3LjM1MDkgMzAuNTU4IDIwQzMwLjU1OCAyMi42NTA4IDMyLjYyOTkgMjQuNzIxIDM1LjI3OSAyNC43MjFDMzcuOTI5OCAyNC43MjEgNDAgMjIuNjQ5MSA0MCAyMEM0MCAxNy4zNTA5IDM3LjkyODEgMTUuMjc5IDM1LjI3OSAxNS4yNzlaTTIwIDBDMTcuMzQ5MiAwIDE1LjI3OSAyLjA3MTkgMTUuMjc5IDQuNzIwOTlDMTUuMjc5IDcuMzcxNzYgMTcuMzUwOSA5LjQ0MTk5IDIwIDkuNDQxOTlDMjIuNjUwOCA5LjQ0MTk5IDI0LjcyMSA3LjM3MDA5IDI0LjcyMSA0LjcyMDk5QzI0LjcyMSAyLjA3MTkgMjIuNjUwOCAwIDIwIDBaTTM1LjI3OSA5LjQ0MTk5QzM3LjkyOTggOS40NDE5OSA0MCA3LjM3MDA5IDQwIDQuNzIwOTlDNDAgMi4wNzAyMyAzNy45MjgxIDAgMzUuMjc5IDBDMzIuNjI4MiAwIDMwLjU1OCAyLjA3MTkgMzAuNTU4IDQuNzIwOTlDMzAuNTU4IDcuMzcxNzYgMzIuNjI4MiA5LjQ0MTk5IDM1LjI3OSA5LjQ0MTk5Wk00LjcyMDk5IDBDMi4wNzE5IDAgMCAyLjA3MTkgMCA0LjcyMDk5QzAgNy4zNzE3NiAyLjA3MTkgOS40NDE5OSA0LjcyMDk5IDkuNDQxOTlDNy4zNzE3NiA5LjQ0MTk5IDkuNDQxOTkgNy4zNzAwOSA5LjQ0MTk5IDQuNzIwOTlDOS40NDE5OSAyLjA3MTkgNy4zNzE3NiAwIDQuNzIwOTkgMFoiCiAgPjwvcGF0aD4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICBpZD0icGFpbnQwX2xpbmVhcl9mb290ZXJfbG9nbyIKICAgICAgeDE9IjEuMTI3NiIKICAgICAgeTE9IjEuNjA2MjkiCiAgICAgIHgyPSIzOC4zOTQxIgogICAgICB5Mj0iMzguODcyOCIKICAgICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgICA+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzAxNjFGRCI+PC9zdG9wPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuMjQ1NyIgc3RvcC1jb2xvcj0iIzNGNDZEMiI+PC9zdG9wPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNTA3OSIgc3RvcC1jb2xvcj0iIzgxMkVBNCI+PC9zdG9wPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNzcyNiIgc3RvcC1jb2xvcj0iI0MyMTU3OCI+PC9zdG9wPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRDAwNTAiPjwvc3RvcD4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgo8L3N2Zz4K',
}

export const WithLogo = () => (
<GridContainer>
<GridRow>
<GridColumn span={['12/12', '12/12', '6/12']} paddingBottom={3}>
<CategoryCard
colorScheme="red"
heading="Fæðingarorlof"
text="Meðal annars fæðingarorlof, nöfn, forsjá, gifting og skilnaður."
{...imageProps}
/>
</GridColumn>
<GridColumn span={['12/12', '12/12', '6/12']} paddingBottom={3}>
<CategoryCard
colorScheme="red"
heading="Fjármál"
text="Meðal annars fæðingarorlof, nöfn, forsjá, gifting og skilnaður."
{...imageProps}
/>
</GridColumn>
</GridRow>
</GridContainer>
)

export const AutoStackLogo = () => (
<GridContainer>
<GridRow>
<GridColumn span={['12/12', '12/12', '6/12']} paddingBottom={3}>
<CategoryCard
colorScheme="red"
heading="Fæðingarorlof"
text={`The image is automatically stacked below the content once the card width goes below the given 'stackWidth' value. Defaults to ${STACK_WIDTH}px.`}
{...imageProps}
autoStack
stackWidth={200}
/>
</GridColumn>
</GridRow>
</GridContainer>
)
220 changes: 166 additions & 54 deletions libs/island-ui/core/src/lib/CategoryCard/CategoryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
import React from 'react'
import React, { ReactElement, ReactNode, forwardRef } from 'react'
import cn from 'classnames'
import { ObjectFitProperty } from 'csstype'
import { useMeasure } from 'react-use'
import { UseMeasureRef } from 'react-use/lib/useMeasure'

import { Box } from '../Box/Box'
import { FocusableBox } from '../FocusableBox/FocusableBox'
import { Inline } from '../Inline/Inline'
import { Tag } from '../Tag/Tag'
import { Hyphen } from '../Hyphen/Hyphen'
import { Text, TextProps } from '../Text/Text'

import * as styles from './CategoryCard.css'

export const STACK_WIDTH = 280

type Tag = {
label: string
href?: string
onClick?: () => void
disabled?: boolean
}

type CategoryCardProps = {
export type CategoryCardProps = {
ref?: UseMeasureRef<HTMLElement>
width?: number
heading: string
headingAs?: TextProps['as']
headingVariant?: TextProps['variant']
text: string
tags?: Tag[]
href?: string
colorScheme?: 'blue' | 'purple' | 'red'
/** The heading above is truncated instead of overflowing */
truncateHeading?: TextProps['truncate']
/** Use event listener to check wether or not to place the image below the text content */
autoStack?: boolean
/** The card width breakpoint that the image should stack below content when autoStack = true */
stackWidth?: number
/** Hyphenate the heading */
hyphenate?: boolean
}

const colorSchemes = {
Expand All @@ -41,62 +60,155 @@ const colorSchemes = {
},
} as const

export const CategoryCard = ({
heading,
headingAs = 'h3',
headingVariant = 'h3',
text,
href = '/',
tags = [],
colorScheme = 'blue',
truncateHeading = false,
}: CategoryCardProps) => {
const hasTags = Array.isArray(tags) && tags.length > 0
const { borderColor, textColor, tagVariant } = colorSchemes[colorScheme]
export type CategoryCardImage =
| {
src: string
alt: string
objectFit?: ObjectFitProperty
customImage?: never
}
| {
src?: never
alt?: never
objectFit?: never
customImage?: ReactNode
}

const Component = forwardRef<
HTMLElement,
CategoryCardProps & CategoryCardImage
>(
(
{
width,
stackWidth = STACK_WIDTH,
heading,
headingAs = 'h3',
headingVariant = 'h3',
text,
href = '/',
tags = [],
colorScheme = 'blue',
truncateHeading = false,
src,
alt,
objectFit = 'contain',
customImage,
hyphenate = false,
autoStack,
},
ref,
) => {
const { borderColor, textColor, tagVariant } = colorSchemes[colorScheme]

return (
<FocusableBox
href={href}
display="flex"
flexDirection="column"
paddingY={3}
paddingX={4}
borderRadius="large"
borderColor={borderColor}
borderWidth="standard"
height="full"
background="white"
color={colorScheme}
>
<Text
as={headingAs}
variant={headingVariant}
color={textColor}
truncate={truncateHeading}
title={heading}
const hasTags = Array.isArray(tags) && tags.length > 0
const hasImage = !!src || !!customImage

const shouldStack = width && width < stackWidth

return (
<FocusableBox
href={href}
display="flex"
flexDirection="row"
paddingY={3}
paddingX={4}
borderRadius="large"
borderColor={borderColor}
borderWidth="standard"
height="full"
width="full"
background="white"
color={colorScheme}
>
{heading}
</Text>
<Text paddingTop={1}>{text}</Text>
{hasTags && (
<Box paddingTop={3}>
<Inline space={['smallGutter', 'smallGutter', 'gutter']}>
{tags.map((tag) => (
<Tag
key={tag.label}
disabled={tag.disabled}
outlined={!tag.href}
variant={tagVariant}
href={tag.href}
onClick={tag.onClick}
truncate={true}
<Box
ref={ref}
display="flex"
flexDirection={shouldStack ? 'column' : 'row'}
justifyContent="center"
alignItems="center"
height="full"
width="full"
>
<Box display="flex" height="full" width="full" flexDirection="column">
<Text
as={headingAs}
variant={headingVariant}
color={textColor}
truncate={truncateHeading}
title={heading}
>
{hyphenate ? <Hyphen>{heading}</Hyphen> : heading}
</Text>
<Text paddingTop={1}>{text}</Text>
{hasTags && (
<Box paddingTop={3}>
<Inline space={['smallGutter', 'smallGutter', 'gutter']}>
{tags.map((tag) => (
<Tag
key={tag.label}
disabled={tag.disabled}
outlined={!tag.href}
variant={tagVariant}
href={tag.href}
onClick={tag.onClick}
truncate={true}
>
{tag.label}
</Tag>
))}
</Inline>
</Box>
)}
</Box>
{hasImage &&
(customImage ? (
customImage
) : (
<Box
display="flex"
height="full"
width="full"
justifyContent="center"
alignItems={shouldStack ? 'flexEnd' : 'center'}
marginLeft={shouldStack ? 0 : 2}
marginTop={shouldStack ? 2 : 0}
className={cn(styles.imageContainer, {
[styles.imageContainerStacked]: shouldStack,
[styles.imageContainerHidden]: !autoStack,
})}
>
{tag.label}
</Tag>
<img src={src} alt={alt} style={{ objectFit }} />
</Box>
))}
</Inline>
</Box>
)}
</FocusableBox>
</FocusableBox>
)
},
)

export const CategoryCard = (props: CategoryCardProps & CategoryCardImage) => {
return props.autoStack ? (
<WithMeasureProps>
{(measureProps) => <Component {...props} {...measureProps} />}
</WithMeasureProps>
) : (
<Component {...props} />
)
}

interface MeasureProps {
children: ({
ref,
width,
}: {
ref: UseMeasureRef<HTMLElement>
width: number
}) => ReactElement | null
}

const WithMeasureProps = ({ children }: MeasureProps) => {
const [ref, { width }] = useMeasure()

return typeof children === 'function' ? children({ ref, width }) : children
}

0 comments on commit eb719d2

Please sign in to comment.