Skip to content

Commit

Permalink
feat(UsaIconListItem): implement UsaIconListItem component
Browse files Browse the repository at this point in the history
ISSUES CLOSED: #214
  • Loading branch information
patrickcate committed May 18, 2022
1 parent a1cdaa1 commit 7377534
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 0 deletions.
126 changes: 126 additions & 0 deletions src/components/UsaIconListItem/UsaIconListItem.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import UsaIconListItem from './UsaIconListItem.vue'

const defaultProps = {
// Name is required.
icon: 'flag',
title: UsaIconListItem.props.title.default,
titleTag: UsaIconListItem.props.titleTag.default,
customClasses: UsaIconListItem.props.customClasses.default(),
}

export default {
component: UsaIconListItem,
title: 'Components/UsaIconListItem',
argTypes: {
icon: {
control: { type: 'text' },
},
title: {
control: { type: 'text' },
},
titleTag: {
options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
control: {
type: 'select',
},
},
customClasses: {
control: { type: 'object' },
},
iconSlot: {
control: { type: 'text' },
},
titleSlot: {
control: { type: 'text' },
},
defaultSlot: {
control: { type: 'text' },
},
},
args: {
icon: defaultProps.icon,
title: defaultProps.title,
titleTag: defaultProps.titleTag,
customClasses: defaultProps.customClasses,
iconSlot: '',
titleSlot: '',
defaultSlot:
'<p>An icon list reinforces the meaning and visibility of individual list items with a leading icon.</p>',
},
decorators: [
() => ({
template: '<ul class="usa-icon-list"><story /></ul>',
}),
],
}

const DefaultTemplate = (args, { argTypes }) => ({
components: { UsaIconListItem },
props: Object.keys(argTypes),
setup() {
return { ...args }
},
template: `<UsaIconListItem
:icon="icon"
:title="title"
:title-tag="titleTag"
:custom-classes="customClasses"
>
<template v-if="${!!args.iconSlot}" #icon>${args.iconSlot}</template>
<template v-if="${!!args.titleSlot}" #title>${args.titleSlot}</template>
<template v-if="${!!args.defaultSlot}" #default>${
args.defaultSlot
}</template>
</UsaIconListItem>`,
})

export const DefaultIconListItem = DefaultTemplate.bind({})
DefaultIconListItem.args = {
...defaultProps,
}
DefaultIconListItem.storyName = 'Default'

export const TitleIconListItem = DefaultTemplate.bind({})
TitleIconListItem.args = {
...defaultProps,
icon: 'bug_report',
title: 'Icon list item title',
}
TitleIconListItem.storyName = 'Item Title'

export const TitleTagIconListItem = DefaultTemplate.bind({})
TitleTagIconListItem.args = {
...defaultProps,
icon: 'format_size',
title: 'Icon list item title',
titleTag: 'h4',
}
TitleTagIconListItem.storyName = 'Custom Title Tag'

export const IconSlotIconListItem = DefaultTemplate.bind({})
IconSlotIconListItem.args = {
...defaultProps,
title: 'Icon list item title',
iconSlot: `<svg class="usa-icon" aria-hidden="true" focusable="false" role="img"><use xlink:href="/assets/img/sprite.svg#push_pin"></use></svg>`,
}
IconSlotIconListItem.storyName = 'Icon Slot'

export const TitleSlotIconListItem = DefaultTemplate.bind({})
TitleSlotIconListItem.args = {
...defaultProps,
icon: 'directions',
titleSlot: '<em>Icon slot title</em>',
}
TitleSlotIconListItem.storyName = 'Title Slot'

export const CustomClassesIconListItem = DefaultTemplate.bind({})
CustomClassesIconListItem.args = {
...defaultProps,
icon: 'chat',
customClasses: {
icon: ['test-icon-class'],
content: ['test-content-class'],
title: ['test-title-class'],
},
}
CustomClassesIconListItem.storyName = 'Custom Classes'
81 changes: 81 additions & 0 deletions src/components/UsaIconListItem/UsaIconListItem.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import '@module/uswds/dist/css/uswds.min.css'
import { mount } from '@cypress/vue'
import UsaIconListItem from './UsaIconListItem.vue'
import { h } from 'vue'

describe('UsaIconListItem', () => {
it('renders the component', () => {
mount(UsaIconListItem, {
props: {
icon: 'flag',
},
slots: {
default: () => h('p', {}, 'Test item content'),
},
})

cy.get('li.usa-icon-list__item').should('exist')
cy.get('div.usa-icon-list__icon > svg.usa-icon').should('exist')
cy.get('h2.usa-icon-list__title').should('not.exist')
cy.get('div.usa-icon-list__content > p').should(
'contain',
'Test item content'
)
})

it('renders custom title tag and slot content', () => {
mount(UsaIconListItem, {
props: {
icon: 'flag',
title: 'TestTitleProp',
titleTag: 'h3',
},
slots: {
icon: () => h('span', { class: 'test-icon-slot' }, 'Test icon slot'),
title: () => 'Test item title slot',
default: () => 'Test item content',
},
})

cy.get('.usa-icon-list__icon > span.test-icon-slot').should(
'contain',
'Test icon slot'
)
cy.get('h3.usa-icon-list__title').should('contain', 'Test item title slot')
})

it('title heading element displays title prop text', () => {
mount(UsaIconListItem, {
props: {
icon: 'flag',
title: 'Test item title',
},
slots: {
default: () => 'Test item content',
},
})

cy.get('h2.usa-icon-list__title').should('contain', 'Test item title')
})

it('adds custom CSS classes', () => {
mount(UsaIconListItem, {
props: {
icon: 'flag',
title: 'Test item title',
customClasses: {
icon: ['test-icon-class'],
content: ['test-content-class'],
title: ['test-title-class'],
},
},
slots: {
default: () => 'Test item content',
},
})

cy.get('.usa-icon-list__icon').should('have.class', 'test-icon-class')
cy.get('.usa-icon-list__content').should('have.class', 'test-content-class')
cy.get('.usa-icon-list__title').should('have.class', 'test-title-class')
})
})
47 changes: 47 additions & 0 deletions src/components/UsaIconListItem/UsaIconListItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup>
import BaseHeading from '@/components/BaseHeading'
import UsaIcon from '@/components/UsaIcon'
defineProps({
icon: {
type: String,
required: true,
},
title: {
type: String,
default: '',
},
titleTag: {
type: String,
default: 'h2',
},
customClasses: {
type: Object,
default: () => {
return {
icon: [],
content: [],
title: [],
}
},
},
})
</script>

<template>
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon" :class="customClasses?.icon">
<slot name="icon"><UsaIcon :name="icon"></UsaIcon></slot>
</div>
<div class="usa-icon-list__content" :class="customClasses?.content">
<BaseHeading
v-if="title || $slots.title"
:tag="titleTag"
class="usa-icon-list__title"
:class="customClasses?.title"
><slot name="title">{{ title }}</slot></BaseHeading
>
<slot></slot>
</div>
</li>
</template>
4 changes: 4 additions & 0 deletions src/components/UsaIconListItem/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import UsaIconListItem from './UsaIconListItem.vue'

export { UsaIconListItem }
export default UsaIconListItem
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,4 @@ export { default as UsaTextarea } from './UsaTextarea'
export { default as UsaTextInput } from './UsaTextInput'
export { default as UsaValidation } from './UsaValidation'
export { default as UsaIcon } from './UsaIcon'
export { default as UsaIconListItem } from './UsaIconListItem'

0 comments on commit 7377534

Please sign in to comment.