refactor(ui): add heading component for visual consistency and maintainability

This commit is contained in:
upsiflu 2025-01-10 01:01:34 +01:00
parent 7e19fd23f0
commit c5be51e779
4 changed files with 117 additions and 6 deletions

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
const props = defineProps<{
[H in `h${'1' | '2' | '3' | '4' | '5' | '6' | '7'}`]? : true
} & {[S in 'page-heading' | 'section-heading' | 'subsection-heading' | 'caption' | 'title' | 'radio' | 'secondary' ]? : true}>()
const level = Object.entries(props).find(([key, value]) => value && key.startsWith('h'))?.[0] || 'h1'
const size = Object.entries(props).find(([key, value]) => value && key!==level)?.[0] || 'section-heading'
</script>
<template>
<component :is="level" :class="size" style="margin: 0; padding:0; line-height: 40px; vertical-align: baseline;">
<slot />
</component>
</template>
<style scoped>
/* Page heading */
.pageHeading { font-size: 36px; font-weight: 900; letter-spacing: -1px; }
/* Section heading, Modal heading [DEFAULT] */
.sectionHeading { font-size: 20px; font-weight: 700; letter-spacing: -.5px; }
/* Form subsection */
.subsectionHeading {font-size: 16px; font-weight: 600; letter-spacing: 0; }
/* input caption */
.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; }
/* Primary radio title */
.radio { font-size: 28px; font-weight: 900; letter-spacing: -.5px; }
/* Secondary radio title */
.secondary { font-size: 28px; font-weight: 300; letter-spacing: -.5px; }
</style>

View File

@ -6,6 +6,7 @@ import Layout from '~/components/ui/Layout.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Button from '~/components/ui/Button.vue'
import Link from '~/components/ui/Link.vue'
import Heading from '~/components/ui/Heading.vue'
const props = defineProps<{
[M in 'no-items' | 'tiny-items' | 'small-items' | 'medium-items']?: true }
@ -14,9 +15,9 @@ const props = defineProps<{
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
const [headingLevel, title] =
props.h1 ? ['h1', props.h1]
: props.h2 ? ['h2', props.h2]
: ['h3', props.h3]
props.h1 ? ['h1', props.h1] as const
: props.h2 ? ['h2', props.h2] as const
: ['h3', props.h3] as const
const numberOfColumnsPerItem =
'noItems' in props && props.noItems ? 1 : 'tinyItems' in props && props.tinyItems ? 2 : 'smallItems' in props && props['smallItems'] ? 3 : 4
@ -44,10 +45,15 @@ const headerGrid =
style="align-self: baseline;"
/>
<!-- Flexible row content -->
<!-- Note that the `h3` uses its padding to create the 24px bottom gap -->
<component :is="headingLevel" style="align-self: baseline; padding:0 0 24px 0; margin:0;">
<Heading h1 v-if="headingLevel==='h1'" pageHeading style="align-self: baseline; padding:0 0 24px 0; margin:0;">
{{ title }}
</component>
</Heading>
<Heading h2 v-if="headingLevel==='h2'" sectionHeading style="align-self: baseline; padding:0 0 24px 0; margin:0;">
{{ title }}
</Heading>
<Heading h3 v-if="headingLevel==='h3'" sectionHeading style="align-self: baseline; padding:0 0 24px 0; margin:0;">
{{ title }}
</Heading>
<Spacer grow />
<!-- Action! You can either specify `to` or `onClick`. -->
<Button v-if="props.action && 'onClick' in props.action"

View File

@ -45,6 +45,7 @@ export default defineConfig({
{ text: 'Toggle', link: '/components/ui/toggle' },
],
},
{ text: 'Heading', link: '/components/ui/heading' },
{
text: 'Layout', link: '/components/ui/layout/',
items: [

View File

@ -0,0 +1,67 @@
<script setup>
import Heading from "~/components/ui/Heading.vue"
</script>
```ts
import Heading from "~/components/ui/Heading.vue";
```
# Heading
Use a heading when the content or the page structure requires it. Define the visual style independently from the logical hierarchy.
- Each page has exactly one `h1` with the visual size `page-heading`.
- Headings always describe the content below. Do not use headings for input captions.
- Try to avoid gaps in the hierarchy.
## Semantic heading level
Use heading levels wherever you want to add logical (not necessarily visual) hierarchy.
Consult [the allyproject for a comprehensive guide on headings](https://www.a11yproject.com/posts/how-to-accessible-heading-structure/).
## Visual sizes for page sections and subsections
---
<Heading h1 page-heading>Page heading</Heading>
Use this visual size on the main heading of the page.
---
<Heading h3 section-heading>Section heading</Heading>
Use section headings to subdivide the main content on the page. Also use for modal headings. This is the default visual size.
---
<Heading h3 subsection-heading>Subsection heading</Heading>
Use subsection headings to break long sections or forms with several groups into digestible subsections.
## Visual sizes for special elements
---
<Heading h4 caption>Caption</Heading>
Caption-style headings are found only within forms.
---
<Heading h3 title>Title</Heading>
Use this visual size to title [Tabs](/components/ui/tabs), Channels, [Cards](/components/ui/card) and [Activities](/components/ui/activity).
---
<Heading h3 radio>Radio</Heading>
Radio cards have giant titles.
---
<Heading h3 secondary>Secondary</Heading>
A card may have a secondary title, [as exemplified in the designs](https://design.funkwhale.audio/#/workspace/a4e0101a-252c-80ef-8003-918b4c2c3927/e3a187f0-0f5e-11ed-adb9-fff9e854a67c?page-id=5e293790-52d3-11ed-9497-8deeaf0bfa97).