refactor(ui): add heading component for visual consistency and maintainability
This commit is contained in:
parent
7e19fd23f0
commit
c5be51e779
|
@ -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>
|
|
@ -6,6 +6,7 @@ import Layout from '~/components/ui/Layout.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import Link from '~/components/ui/Link.vue'
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
import Heading from '~/components/ui/Heading.vue'
|
||||||
|
|
||||||
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 }
|
||||||
|
@ -14,9 +15,9 @@ const props = defineProps<{
|
||||||
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
|
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
|
||||||
|
|
||||||
const [headingLevel, title] =
|
const [headingLevel, title] =
|
||||||
props.h1 ? ['h1', props.h1]
|
props.h1 ? ['h1', props.h1] as const
|
||||||
: props.h2 ? ['h2', props.h2]
|
: props.h2 ? ['h2', props.h2] as const
|
||||||
: ['h3', props.h3]
|
: ['h3', props.h3] as const
|
||||||
|
|
||||||
const numberOfColumnsPerItem =
|
const numberOfColumnsPerItem =
|
||||||
'noItems' in props && props.noItems ? 1 : 'tinyItems' in props && props.tinyItems ? 2 : 'smallItems' in props && props['smallItems'] ? 3 : 4
|
'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;"
|
style="align-self: baseline;"
|
||||||
/>
|
/>
|
||||||
<!-- Flexible row content -->
|
<!-- Flexible row content -->
|
||||||
<!-- Note that the `h3` uses its padding to create the 24px bottom gap -->
|
<Heading h1 v-if="headingLevel==='h1'" pageHeading style="align-self: baseline; padding:0 0 24px 0; margin:0;">
|
||||||
<component :is="headingLevel" style="align-self: baseline; padding:0 0 24px 0; margin:0;">
|
|
||||||
{{ title }}
|
{{ 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 />
|
<Spacer grow />
|
||||||
<!-- Action! You can either specify `to` or `onClick`. -->
|
<!-- Action! You can either specify `to` or `onClick`. -->
|
||||||
<Button v-if="props.action && 'onClick' in props.action"
|
<Button v-if="props.action && 'onClick' in props.action"
|
||||||
|
|
|
@ -45,6 +45,7 @@ export default defineConfig({
|
||||||
{ text: 'Toggle', link: '/components/ui/toggle' },
|
{ text: 'Toggle', link: '/components/ui/toggle' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ text: 'Heading', link: '/components/ui/heading' },
|
||||||
{
|
{
|
||||||
text: 'Layout', link: '/components/ui/layout/',
|
text: 'Layout', link: '/components/ui/layout/',
|
||||||
items: [
|
items: [
|
||||||
|
|
|
@ -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).
|
Loading…
Reference in New Issue