121 lines
3.5 KiB
Vue
121 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import type { RouterLinkProps } from 'vue-router'
|
|
import { useAttrs } from 'vue'
|
|
|
|
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 actionComponents
|
|
= { Button, Link }
|
|
|
|
const props = defineProps<{
|
|
[M in 'no-items' | 'tiny-items' | 'small-items' | 'medium-items']?: true }
|
|
& {
|
|
alignLeft?: boolean;
|
|
collapsed?: boolean;
|
|
}
|
|
& { [H in 'h1' | 'h2' | 'h3']?: string }
|
|
& { action?: { text: string } & (RouterLinkProps | { onClick: (...args: any[]) => void | Promise<void> }) }>()
|
|
|
|
const heading
|
|
= props.h1
|
|
? ({ h1: props.h1, pageHeading: true }) 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
|
|
|
|
const { style, ...fallthroughProps } = useAttrs()
|
|
|
|
const headerGrid
|
|
= `auto / repeat(auto-fit, calc(46px * ${numberOfColumnsPerItem} + 32px * ${numberOfColumnsPerItem - 1}))`
|
|
</script>
|
|
|
|
<template>
|
|
<section style="flex-grow: 1;">
|
|
<Layout
|
|
header
|
|
:grid="headerGrid"
|
|
:style="`${'alignLeft' in props && props.alignLeft ? 'justify-content: start' : ''};
|
|
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;"
|
|
>
|
|
<!-- 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 />
|
|
<!-- Action! You can either specify `to` or `onClick`. -->
|
|
<component
|
|
:is="'onClick' in action ? actionComponents.Button : actionComponents.Link"
|
|
v-if="action"
|
|
ghost
|
|
thin-font
|
|
align-self="baseline"
|
|
:align-text="'collapsed' in props ? 'left' : undefined"
|
|
:aria-pressed="props.collapsed === false || undefined"
|
|
:class="{
|
|
[$style.action]: true,
|
|
[$style.transparent]: 'primary' in props || 'secondary' in props || 'destructive' in props,
|
|
[$style.full]: 'collapsed' in props
|
|
}"
|
|
v-bind="{...fallthroughProps, ...action, ['collapsed' in props ? 'full' : 'min-content']: true}"
|
|
>
|
|
{{ action?.text }}
|
|
</component>
|
|
</Layout>
|
|
</Layout>
|
|
|
|
<!-- Love: https://css-tricks.com/css-grid-can-do-auto-height-transitions/ -->
|
|
|
|
<Layout
|
|
main
|
|
:inert="'collapsed' in props && props.collapsed"
|
|
:style="`${
|
|
'alignLeft' in props && props.alignLeft
|
|
? 'justify-content: start;'
|
|
: ''
|
|
} ${
|
|
'collapsed' in props && props.collapsed
|
|
? 'grid-template-rows: 0fr; overflow: hidden; max-height: 0;'
|
|
: 'max-height: 4000px;'
|
|
}
|
|
position: relative;
|
|
transition: max-height .5s, grid-template-rows .3s;`
|
|
"
|
|
grid="auto / repeat(auto-fit, 46px)"
|
|
>
|
|
<slot />
|
|
</Layout>
|
|
</section>
|
|
</template>
|
|
|
|
<style module lang="scss">
|
|
|
|
.action {
|
|
&.transparent {
|
|
margin-right: 16px;
|
|
}
|
|
&.full {
|
|
min-width: 100%;
|
|
}
|
|
}
|
|
</style>
|