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 />
|
||||
<UploadModal v-if="store.state.auth.authenticated" />
|
||||
<SearchModal />
|
||||
<ServiceMessages />
|
||||
</div>
|
||||
</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;}
|
||||
|
||||
/* 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] */
|
||||
/* TODO: Decide on a size. All mockups on https://design.funkwhale.audio/ have 20px. */
|
||||
.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 */
|
||||
.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 */
|
||||
.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 */
|
||||
.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 */
|
||||
.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 { font-size: 28px; font-weight: 300; letter-spacing: -.5px; }
|
||||
:is(*, .vp-doc h3).secondary { font-size: 28px; font-weight: 300; letter-spacing: -.5px; }
|
||||
</style>
|
||||
|
|
|
@ -12,13 +12,17 @@ const actionComponents
|
|||
= { Button, Link }
|
||||
|
||||
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;
|
||||
collapsed?: boolean;
|
||||
action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> })
|
||||
}
|
||||
& {
|
||||
[Operation in 'expand' | 'collapse']?: () => void
|
||||
}
|
||||
& { [H in 'h1' | 'h2' | 'h3']?: string }
|
||||
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
|
||||
>()
|
||||
|
||||
const heading
|
||||
= props.h1
|
||||
|
@ -41,27 +45,65 @@ const headerGrid
|
|||
<Layout
|
||||
header
|
||||
:grid="headerGrid"
|
||||
:style="`${'alignLeft' in props && props.alignLeft ? 'justify-content: start' : ''};
|
||||
margin-top: -64px;`"
|
||||
:style="`
|
||||
${ '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 -->
|
||||
<Layout
|
||||
flex
|
||||
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 -->
|
||||
<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 v-if="expand || collapse">
|
||||
<Button
|
||||
full
|
||||
align-text="left"
|
||||
align-self="end"
|
||||
:class="$style.summary"
|
||||
:aria-pressed="!!collapse"
|
||||
raised
|
||||
@click="() => expand ? expand() : collapse ? collapse() : (() => { return })()"
|
||||
>
|
||||
<Heading
|
||||
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`. -->
|
||||
<component
|
||||
:is="'onClick' in action ? actionComponents.Button : actionComponents.Link"
|
||||
|
@ -69,14 +111,14 @@ const headerGrid
|
|||
ghost
|
||||
thin-font
|
||||
align-self="baseline"
|
||||
:align-text="'collapsed' in props ? 'left' : undefined"
|
||||
:aria-pressed="props.collapsed === false || undefined"
|
||||
:align-text="expand || collapse ? 'left' : undefined"
|
||||
:aria-pressed="collapse"
|
||||
:class="{
|
||||
[$style.action]: true,
|
||||
[$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 }}
|
||||
</component>
|
||||
|
@ -87,18 +129,22 @@ const headerGrid
|
|||
|
||||
<Layout
|
||||
main
|
||||
:inert="'collapsed' in props && props.collapsed"
|
||||
:inert="!!expand"
|
||||
:style="`${
|
||||
'alignLeft' in props && props.alignLeft
|
||||
? 'justify-content: start;'
|
||||
: ''
|
||||
} ${
|
||||
'collapsed' in props && props.collapsed
|
||||
}${
|
||||
!!expand
|
||||
? 'grid-template-rows: 0fr; overflow: hidden; max-height: 0;'
|
||||
: 'max-height: 4000px;'
|
||||
}${
|
||||
!!collapse
|
||||
? 'padding: 12px 0;'
|
||||
: ''
|
||||
}
|
||||
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)"
|
||||
>
|
||||
|
@ -108,6 +154,12 @@ const headerGrid
|
|||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.summary {
|
||||
align-self: baseline;
|
||||
min-width: calc(100% + 32px);
|
||||
margin: 0 -16px;
|
||||
--fw-border-radius: 32px;
|
||||
}
|
||||
|
||||
.action {
|
||||
&.transparent {
|
||||
|
|
|
@ -99,6 +99,8 @@ const user: User = {
|
|||
is_superuser: true,
|
||||
privacy_level: "everyone"
|
||||
}
|
||||
|
||||
const sections = ref<boolean[]>([false, false, false])
|
||||
</script>
|
||||
|
||||
```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;">
|
||||
|
||||
<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
|
||||
</Card>
|
||||
<Card small title="Relatively Long Album Name">
|
||||
<Card small default solid raised title="Relatively Long Album Name">
|
||||
Artist Name
|
||||
</Card>
|
||||
<Card small title="Relatively Long Album Name">
|
||||
<Card small default solid raised title="Relatively Long Album Name">
|
||||
Artist Name
|
||||
</Card>
|
||||
</Section>
|
||||
|
@ -257,6 +259,48 @@ Note the spacer above the layout. By default, sections begin at the baseline of
|
|||
|
||||
</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
|
||||
|
||||
- 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