Skip to content

Commit

Permalink
Merge pull request #1327 from dpc-sdp/feat/sd-207-accordion-slots
Browse files Browse the repository at this point in the history
[SD-207] Accordion slots
  • Loading branch information
waitingallday authored Sep 26, 2024
2 parents 805e569 + 621ea1a commit 8f1390a
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@storybook/addon-docs'
import { a11yStoryCheck } from './../../../stories/interactions.js'
import RplAccordion from './RplAccordion.vue'
import RplAccordionItem from './RplAccordionItem.vue'
import SAMPLE from './fixtures/default.js'

export const Template = (args) => ({
Expand All @@ -23,6 +24,29 @@ export const Template = (args) => ({
`
})

export const SlotsTemplate = (args) => ({
components: { RplAccordion, RplAccordionItem },
setup() {
return { args }
},
template: `
<RplAccordion :id="args.id">
<RplAccordionItem id="item1">
<template #title>
Title content here
</template>
<p>Body content here</p>
</RplAccordionItem>
<RplAccordionItem id="item2" :active="true">
<template #title>
Title content here
</template>
<p>Body content here</p>
</RplAccordionItem>
</RplAccordion>
`
})

<Meta
title='Core/Containers/Accordion'
component={RplAccordion}
Expand Down Expand Up @@ -55,11 +79,25 @@ export const Template = (args) => ({
name='Accordion numbered'
play={a11yStoryCheck}
args={{
id: 'example',
id: 'example-numbered',
items: SAMPLE,
numbered: true
}}
>
{Template.bind()}
</Story>
</Canvas>

## With slots

<Canvas>
<Story
name='Accordion with slots'
play={a11yStoryCheck}
args={{
id: 'example-slots'
}}
>
{SlotsTemplate.bind()}
</Story>
</Canvas>
182 changes: 78 additions & 104 deletions packages/ripple-ui-core/src/components/accordion/RplAccordion.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<script setup lang="ts">
import { computed } from 'vue'
import RplIcon from '../icon/RplIcon.vue'
import RplContent from '../content/RplContent.vue'
import RplExpandable from '../expandable/RplExpandable.vue'
import { computed, ref, type Ref, provide, useSlots } from 'vue'
import { useExpandableState } from '../../composables/useExpandableState'
import {
useRippleEvent,
rplEventPayload
} from '../../composables/useRippleEvent'
import RplAccordionItem from './RplAccordionItem.vue'
type RplAccordionItem = {
type AccordionItem = {
id: string
title?: string
content: string
Expand All @@ -18,7 +16,7 @@ type RplAccordionItem = {
interface Props {
id: string
items: RplAccordionItem[]
items?: AccordionItem[]
numbered?: boolean
displayToggleAll?: boolean
}
Expand All @@ -42,23 +40,55 @@ const emit = defineEmits<{
const { emitRplEvent } = useRippleEvent('rpl-accordion', emit)
const initialActiveIndexes: string[] = props.items.reduce(
(result: string[], current: RplAccordionItem): string[] => {
if (current.active) {
return [...result, current.id]
}
const slots = useSlots()
return result
},
[]
const itemise = (inputId: string): string => `accordion-${props.id}-${inputId}`
const sharedActiveItems: Ref<string[]> = ref([])
// Prop based items
sharedActiveItems.value.push(
...props.items.reduce(
(result: string[], current: AccordionItem): string[] => {
if (current.active) {
return [...result, itemise(current.id)]
}
return result
},
[]
)
)
// Slot based items
sharedActiveItems.value.push(
...(slots.default
? slots.default().reduce((result: string[], current): string[] => {
if (current.props?.active) {
return [...result, itemise(current.props.id)]
}
return result
}, [])
: [])
)
const itemLength = computed(() =>
props.items.length > 0
? props.items.length
: (slots?.default?.()?.length as number) || 0
)
const { isItemExpanded, isAllExpanded, toggleItem } = useExpandableState(
initialActiveIndexes,
props.items.length
[],
itemLength.value,
sharedActiveItems
)
const itemID = (itemId) => `accordion-${props.id}-${itemId}`
provide('activeItems', {
sharedActiveItems: sharedActiveItems,
totalItems: itemLength.value,
parentId: props.id
})
const toggleAll = () => {
emitRplEvent(
Expand All @@ -73,59 +103,48 @@ const toggleAll = () => {
// Make all items active
if (!isAllExpanded()) {
// If the item is not expanded, make it expanded
props.items.forEach((item) => {
// If the item is not expanded, make it expanded
if (!isItemExpanded(item.id)) {
toggleItem(item.id)
if (!isItemExpanded(itemise(item.id))) {
toggleItem(itemise(item.id))
}
})
slots.default?.().forEach((item) => {
if (!isItemExpanded(itemise(item.props!.id))) {
toggleItem(itemise(item.props!.id))
}
})
}
// Make all items inactive
else {
// If the item is expanded, make it not expanded
props.items.forEach((item) => {
// If the item is expanded, make it not expanded
if (isItemExpanded(item.id)) {
toggleItem(item.id)
if (isItemExpanded(itemise(item.id))) {
toggleItem(itemise(item.id))
}
})
slots.default?.().forEach((item) => {
if (isItemExpanded(itemise(item.props!.id))) {
toggleItem(itemise(item.props!.id))
}
})
}
}
const toggleSingle = (item: RplAccordionItem) => {
emitRplEvent(
'toggleItem',
{
id: itemID(item.id),
action: isItemExpanded(item.id) ? 'close' : 'open',
text: item.title
},
{ global: true }
)
toggleItem(item.id)
}
const toggleAllLabel = computed(() => {
let label = 'Open all'
if (isAllExpanded()) {
label = 'Close all'
}
return label
})
const toggleAllLabel = computed(
() => `${isAllExpanded() ? 'Close' : 'Open'} all`
)
</script>

<template>
<div :id="`accordion-${id}`" class="rpl-accordion">
<!-- Toggle all -->
<div
v-if="displayToggleAll"
v-if="displayToggleAll && itemLength > 1"
class="rpl-accordion__toggle-all-wrapper rpl-u-screen-only"
>
<button
v-if="items.length > 1"
class="rpl-accordion__toggle-all rpl-u-focusable-inline"
@click="toggleAll()"
>
Expand All @@ -134,64 +153,19 @@ const toggleAllLabel = computed(() => {
</div>

<!-- Items -->
<component :is="numbered ? 'ol' : 'ul'" class="rpl-accordion__items">
<li
<component
:is="numbered ? 'ol' : 'ul'"
v-if="itemLength > 0"
class="rpl-accordion__items"
>
<RplAccordionItem
v-for="(item, index) in items"
:id="itemID(item.id)"
:key="item.id"
:class="{
'rpl-accordion__item': true,
'rpl-accordion__item--active': isItemExpanded(item.id)
}"
>
<!-- Item toggle -->
<button
:id="`${itemID(item.id)}-toggle`"
class="rpl-accordion__item-toggle rpl-u-focusable-block"
type="button"
:aria-controls="`${itemID(item.id)}-content`"
:aria-expanded="isItemExpanded(item.id)"
@click="toggleSingle(item)"
>
<span class="rpl-accordion__item-heading-wrapper">
<!-- Number -->
<span
v-if="numbered"
class="rpl-accordion__item-number rpl-type-h4"
>
{{ index + 1 }}
</span>

<!-- Title -->
<span class="rpl-accordion__item-heading rpl-type-h4">
{{ item.title }}
</span>
</span>

<!-- Icon -->
<span
class="rpl-accordion__item-icon rpl-u-screen-only"
aria-hidden="true"
>
<RplIcon name="icon-chevron-down"></RplIcon>
</span>
</button>

<!-- Item content -->
<RplExpandable
:id="`${itemID(item.id)}-content`"
:aria-labelledby="`${itemID(item.id)}-toggle`"
:aria-hidden="isItemExpanded(item.id) === false ? 'true' : null"
:expanded="isItemExpanded(item.id)"
class="rpl-accordion__item-content"
>
<RplContent
class="rpl-accordion__item-content-inner"
:html="item.content"
>
</RplContent>
</RplExpandable>
</li>
:item="item"
:numbered="numbered"
:index="index"
/>
<slot />
</component>
</div>
</template>
Expand Down
Loading

0 comments on commit 8f1390a

Please sign in to comment.