feat(ui): add collapse (Accordion) feature to Section component
This commit is contained in:
parent
45d18dc493
commit
c42f08babe
|
@ -111,6 +111,7 @@ store.dispatch('auth/fetchUser')
|
||||||
<ReportModal />
|
<ReportModal />
|
||||||
<UploadModal v-if="store.state.auth.authenticated" />
|
<UploadModal v-if="store.state.auth.authenticated" />
|
||||||
<SearchModal />
|
<SearchModal />
|
||||||
|
<ServiceMessages />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -26,23 +26,25 @@ const size = computed(() => (Object.entries(props).find(([key, value]) => value
|
||||||
:is(h1, h2, h3, h4, h5, h6).heading { margin: 0; padding:0; vertical-align: baseline; align-self: baseline;}
|
:is(h1, h2, h3, h4, h5, h6).heading { margin: 0; padding:0; vertical-align: baseline; align-self: baseline;}
|
||||||
|
|
||||||
/* Page heading */
|
/* Page heading */
|
||||||
.pageheading { font-size: 36px; font-weight: 900; letter-spacing: -1px; }
|
:is(*, .vp-doc h3).pageheading { font-size: 36px; font-weight: 900; letter-spacing: -1px; }
|
||||||
|
|
||||||
/* Section heading, Modal heading [DEFAULT] */
|
/* Section heading, Modal heading [DEFAULT] */
|
||||||
|
/* TODO: Decide on a size. All mockups on https://design.funkwhale.audio/ have 20px. */
|
||||||
.sectionheading { font-size: 28px; font-weight: 700; letter-spacing: -.5px; }
|
.sectionheading { font-size: 28px; font-weight: 700; letter-spacing: -.5px; }
|
||||||
|
:is(*, .vp-doc h3).sectionheading { font-size: 20px; font-weight: 700; letter-spacing: -.5px; }
|
||||||
|
|
||||||
/* Form subsection */
|
/* Form subsection */
|
||||||
.subsectionheading {font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
:is(*, .vp-doc h3).subsectionheading {font-size: 16px; font-weight: 600; letter-spacing: 0; }
|
||||||
|
|
||||||
/* input caption */
|
/* input caption */
|
||||||
.caption {font-size: 14px; font-weight: 600; letter-spacing: .25px; }
|
:is(*, .vp-doc h3).caption {font-size: 14px; font-weight: 600; letter-spacing: .25px; }
|
||||||
|
|
||||||
/* Tab title, Channel title, Card title, Activity title */
|
/* Tab title, Channel title, Card title, Activity title */
|
||||||
.title { font-size: 16px; font-weight: 700; line-height: 18px; }
|
:is(*, .vp-doc h3).title { font-size: 16px; font-weight: 700; line-height: 18px; }
|
||||||
|
|
||||||
/* Primary radio title */
|
/* Primary radio title */
|
||||||
.radio { font-size: 28px; font-weight: 900; letter-spacing: -.5px; }
|
:is(*, .vp-doc h3).radio { font-size: 28px; font-weight: 900; letter-spacing: -.5px; }
|
||||||
|
|
||||||
/* Secondary radio title */
|
/* Secondary radio title */
|
||||||
.secondary { font-size: 28px; font-weight: 300; letter-spacing: -.5px; }
|
:is(*, .vp-doc h3).secondary { font-size: 28px; font-weight: 300; letter-spacing: -.5px; }
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,13 +12,17 @@ const actionComponents
|
||||||
= { Button, Link }
|
= { Button, Link }
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
[M in 'no-items' | 'tiny-items' | 'small-items' | 'medium-items']?: true }
|
[M in 'no-items' | 'tiny-items' | 'small-items' | 'medium-items']?: true
|
||||||
|
}
|
||||||
& {
|
& {
|
||||||
alignLeft?: boolean;
|
alignLeft?: boolean;
|
||||||
collapsed?: boolean;
|
action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> })
|
||||||
|
}
|
||||||
|
& {
|
||||||
|
[Operation in 'expand' | 'collapse']?: () => void
|
||||||
}
|
}
|
||||||
& { [H in 'h1' | 'h2' | 'h3']?: string }
|
& { [H in 'h1' | 'h2' | 'h3']?: string }
|
||||||
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
|
>()
|
||||||
|
|
||||||
const heading
|
const heading
|
||||||
= props.h1
|
= props.h1
|
||||||
|
@ -41,27 +45,65 @@ const headerGrid
|
||||||
<Layout
|
<Layout
|
||||||
header
|
header
|
||||||
:grid="headerGrid"
|
:grid="headerGrid"
|
||||||
:style="`${'alignLeft' in props && props.alignLeft ? 'justify-content: start' : ''};
|
:style="`
|
||||||
margin-top: -64px;`"
|
${ 'alignLeft' in props && props.alignLeft ? 'justify-content: start' : '' };
|
||||||
|
${ expand || collapse ? '' : 'margin-top: -64px;' }`
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<!-- The title row's width is a multiple of the expected items' column span -->
|
<!-- The title row's width is a multiple of the expected items' column span -->
|
||||||
<Layout
|
<Layout
|
||||||
flex
|
flex
|
||||||
no-gap
|
no-gap
|
||||||
style="grid-column: 1 / -1; align-self: baseline;"
|
style="
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
align-self: baseline;
|
||||||
|
position: relative;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<!-- Set distance between baseline and previous row -->
|
<template v-if="expand || collapse">
|
||||||
<Spacer
|
<Button
|
||||||
v
|
full
|
||||||
:size="64"
|
align-text="left"
|
||||||
style="align-self: baseline;"
|
align-self="end"
|
||||||
/>
|
:class="$style.summary"
|
||||||
<Heading
|
:aria-pressed="!!collapse"
|
||||||
v-if="heading"
|
raised
|
||||||
v-bind="heading"
|
@click="() => expand ? expand() : collapse ? collapse() : (() => { return })()"
|
||||||
style="align-self: baseline; padding:0 0 24px 0; margin:0;"
|
>
|
||||||
/>
|
<Heading
|
||||||
<Spacer grow />
|
v-if="heading"
|
||||||
|
v-bind="heading"
|
||||||
|
caption
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<i
|
||||||
|
:class="!!expand ? 'bi bi-chevron-down' : 'bi bi-chevron-up'"
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- Set distance between baseline and previous row -->
|
||||||
|
<Spacer
|
||||||
|
v
|
||||||
|
:size="64"
|
||||||
|
style="align-self: baseline;"
|
||||||
|
/>
|
||||||
|
<Heading
|
||||||
|
v-if="heading"
|
||||||
|
v-bind="heading"
|
||||||
|
style="
|
||||||
|
align-self: baseline;
|
||||||
|
padding: 0 0 24px 0;
|
||||||
|
margin: 0;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Spacer grow />
|
||||||
|
</template>
|
||||||
<!-- Action! You can either specify `to` or `onClick`. -->
|
<!-- Action! You can either specify `to` or `onClick`. -->
|
||||||
<component
|
<component
|
||||||
:is="'onClick' in action ? actionComponents.Button : actionComponents.Link"
|
:is="'onClick' in action ? actionComponents.Button : actionComponents.Link"
|
||||||
|
@ -69,14 +111,14 @@ const headerGrid
|
||||||
ghost
|
ghost
|
||||||
thin-font
|
thin-font
|
||||||
align-self="baseline"
|
align-self="baseline"
|
||||||
:align-text="'collapsed' in props ? 'left' : undefined"
|
:align-text="expand || collapse ? 'left' : undefined"
|
||||||
:aria-pressed="props.collapsed === false || undefined"
|
:aria-pressed="collapse"
|
||||||
:class="{
|
:class="{
|
||||||
[$style.action]: true,
|
[$style.action]: true,
|
||||||
[$style.transparent]: 'primary' in props || 'secondary' in props || 'destructive' in props,
|
[$style.transparent]: 'primary' in props || 'secondary' in props || 'destructive' in props,
|
||||||
[$style.full]: 'collapsed' in props
|
[$style.full]: expand || collapse
|
||||||
}"
|
}"
|
||||||
v-bind="{...fallthroughProps, ...action, ['collapsed' in props ? 'full' : 'min-content']: true}"
|
v-bind="{ ...fallthroughProps, ...action, [expand || collapse ? 'full' : 'min-content']: true }"
|
||||||
>
|
>
|
||||||
{{ action?.text }}
|
{{ action?.text }}
|
||||||
</component>
|
</component>
|
||||||
|
@ -87,18 +129,22 @@ const headerGrid
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
main
|
main
|
||||||
:inert="'collapsed' in props && props.collapsed"
|
:inert="!!expand"
|
||||||
:style="`${
|
:style="`${
|
||||||
'alignLeft' in props && props.alignLeft
|
'alignLeft' in props && props.alignLeft
|
||||||
? 'justify-content: start;'
|
? 'justify-content: start;'
|
||||||
: ''
|
: ''
|
||||||
} ${
|
}${
|
||||||
'collapsed' in props && props.collapsed
|
!!expand
|
||||||
? 'grid-template-rows: 0fr; overflow: hidden; max-height: 0;'
|
? 'grid-template-rows: 0fr; overflow: hidden; max-height: 0;'
|
||||||
: 'max-height: 4000px;'
|
: 'max-height: 4000px;'
|
||||||
|
}${
|
||||||
|
!!collapse
|
||||||
|
? 'padding: 12px 0;'
|
||||||
|
: ''
|
||||||
}
|
}
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: max-height .5s, grid-template-rows .3s;`
|
transition: max-height .5s, grid-template-rows .3s, padding .2s;`
|
||||||
"
|
"
|
||||||
grid="auto / repeat(auto-fit, 46px)"
|
grid="auto / repeat(auto-fit, 46px)"
|
||||||
>
|
>
|
||||||
|
@ -108,6 +154,12 @@ const headerGrid
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
.summary {
|
||||||
|
align-self: baseline;
|
||||||
|
min-width: calc(100% + 32px);
|
||||||
|
margin: 0 -16px;
|
||||||
|
--fw-border-radius: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
&.transparent {
|
&.transparent {
|
||||||
|
|
|
@ -99,6 +99,8 @@ const user: User = {
|
||||||
is_superuser: true,
|
is_superuser: true,
|
||||||
privacy_level: "everyone"
|
privacy_level: "everyone"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sections = ref<boolean[]>([false, false, false])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
@ -238,13 +240,13 @@ Note the spacer above the layout. By default, sections begin at the baseline of
|
||||||
<Layout stack gap-64 class="preview" style="margin: 0 -40px; padding: 0 25px;">
|
<Layout stack gap-64 class="preview" style="margin: 0 -40px; padding: 0 25px;">
|
||||||
|
|
||||||
<Section :alignLeft="alignLeft" small-items h3="Cards (small items)" :action="{ text:'Documentation on Cards', to:'../card' }">
|
<Section :alignLeft="alignLeft" small-items h3="Cards (small items)" :action="{ text:'Documentation on Cards', to:'../card' }">
|
||||||
<Card small title="Relatively Long Album Name">
|
<Card small default solid raised title="Relatively Long Album Name">
|
||||||
Artist Name
|
Artist Name
|
||||||
</Card>
|
</Card>
|
||||||
<Card small title="Relatively Long Album Name">
|
<Card small default solid raised title="Relatively Long Album Name">
|
||||||
Artist Name
|
Artist Name
|
||||||
</Card>
|
</Card>
|
||||||
<Card small title="Relatively Long Album Name">
|
<Card small default solid raised title="Relatively Long Album Name">
|
||||||
Artist Name
|
Artist Name
|
||||||
</Card>
|
</Card>
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -257,6 +259,48 @@ Note the spacer above the layout. By default, sections begin at the baseline of
|
||||||
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
## Collapse and expand the section
|
||||||
|
|
||||||
|
By adding either `collapse` or `expand` to the props, you add Accordion behavior to the section.
|
||||||
|
The heading will become clickable.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const sections = ref([false, false, false])
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue-html
|
||||||
|
<Section
|
||||||
|
v-for="(section, index) in sections"
|
||||||
|
:key="`${index}${section}`"
|
||||||
|
:h3="`Section ${index} (${section})`"
|
||||||
|
v-bind="
|
||||||
|
section
|
||||||
|
? { collapse: () => { console.log('collapse!'); sections[index] = false } }
|
||||||
|
: { expand: () => { console.log('expand!'); sections[index] = true } }
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Content {{ section }}
|
||||||
|
</Section>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Section
|
||||||
|
v-for="(section, index) in sections"
|
||||||
|
:key="`${index}${section}`"
|
||||||
|
:h3="`Section ${index}`"
|
||||||
|
align-left
|
||||||
|
no-items
|
||||||
|
v-bind="
|
||||||
|
section
|
||||||
|
? { collapse: () => { console.log('collapse!'); sections[index] = false } }
|
||||||
|
: { expand: () => { console.log('expand!'); sections[index] = true } }
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
title="Content"
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
## Responsivity
|
## Responsivity
|
||||||
|
|
||||||
- Cards and Activities snap to the grid columns. They have intrinsic widths, expressed in the number of columns they span. For `Card`, it is `3` and for `Activity`, it is `4`.
|
- Cards and Activities snap to the grid columns. They have intrinsic widths, expressed in the number of columns they span. For `Card`, it is `3` and for `Activity`, it is `4`.
|
||||||
|
|
Loading…
Reference in New Issue