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,15 +45,48 @@ 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; | ||||||
|  |         " | ||||||
|       > |       > | ||||||
|  |         <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 --> |           <!-- Set distance between baseline and previous row --> | ||||||
|           <Spacer |           <Spacer | ||||||
|             v |             v | ||||||
|  | @ -59,9 +96,14 @@ const headerGrid | ||||||
|           <Heading |           <Heading | ||||||
|             v-if="heading" |             v-if="heading" | ||||||
|             v-bind="heading" |             v-bind="heading" | ||||||
|           style="align-self: baseline; padding:0  0 24px 0; margin:0;" |             style=" | ||||||
|  |               align-self: baseline; | ||||||
|  |               padding: 0 0 24px 0; | ||||||
|  |               margin: 0; | ||||||
|  |             " | ||||||
|           /> |           /> | ||||||
|           <Spacer grow /> |           <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
	
	 upsiflu
						upsiflu