You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/blocks/site-footer/site-footer.svelte

120 lines
4.2 KiB

<script lang="ts">
/**
* SiteFooter — the end of the page: the brand, the link columns, an optional
* sign-up band, and a bottom bar with the legal line, the social links and
* whatever the app wants to park there (language / theme pickers, D-BLK.6).
*
* FORM: compound for what REPEATS (`Column`, one per link group — the app maps
* over N), snippet slots for what does not (brand, signup, social, legal,
* extra). That is the tier's admission rule, applied part by part.
*
* The landmark is a real `<footer>`, so it is `contentinfo` for free. The
* column titles are `Heading level={3}` — a footer group HAS a name, and the
* references mark it up the same way.
*
* B contract: canon components only — no `.css`, no morfo, no strings of its
* own. It does NOT import the `newsletter` block for the sign-up band (B-10):
* the app composes its own form there.
*/
import { Section } from '$uix/eidos/components/section';
import { Container } from '$uix/eidos/components/container';
import { Stack } from '$uix/eidos/components/stack';
import { Flex } from '$uix/eidos/components/flex';
import { Grid } from '$uix/eidos/components/grid';
import { Box } from '$uix/eidos/components/box';
import { AutoGrid } from '$uix/eidos/components/auto-grid';
import { Separator } from '$uix/eidos/components/separator';
import { Motion } from '$uix/eidos/components/motion';
import type { SiteFooterProps } from './types';
let {
// `lg`, the same measure `hero` / `cta` / `newsletter` default to. A footer
// wider than every other section of the page reads as misaligned — verified
// by composing it under a `lg` section.
containerSize = 'lg',
sectionSize = 'lg',
minChildWidth = '7.5rem',
brand,
signup,
social,
legal,
extra,
children,
...rest
}: SiteFooterProps = $props();
</script>
<footer {...rest}>
<Section size={sectionSize}>
<Container size={containerSize}>
<!--
One entrance for the whole footer. No stagger: nobody reads a footer
column by column, and dealing five of them in would draw the eye to the
least important part of the page.
-->
<Motion trigger="viewport">
<Stack gap={8}>
<!-- Top region: the brand at the inline-start, the columns filling the
rest. Stacks on narrow, where two tracks leave the columns unusable. -->
<!--
`1fr 3fr`, not `1fr 2fr`: measured at the `lg` measure, a 2fr track
only fits three 7.5rem columns, so a four-group footer broke into
3 + 1. The brand needs a quarter; the columns need the rest.
-->
<Grid
templateColumns={{ base: '1fr', md: 'minmax(0, 1fr) minmax(0, 3fr)' }}
gap={{ base: 8, md: 10 }}
align="start"
>
{#if brand}
<Box>{@render brand()}</Box>
{/if}
<!-- Fluid by construction: as many columns as fit, no breakpoints. -->
<!-- Tighter between columns than between wrapped rows: the horizontal
gap is what decides how many groups share a row (five of them fit
at the `lg` measure with `6`, not with `8`), while the vertical one
has to separate two bands of links. -->
<AutoGrid {minChildWidth} columnGap={6} rowGap={8} align="start">
{@render children?.()}
</AutoGrid>
</Grid>
{#if signup}
<!-- Its own band: in the brand column an email row would have to stack. -->
<Box>{@render signup()}</Box>
{/if}
</Stack>
</Motion>
{#if legal || social || extra}
<!-- The bottom bar is outside the `Motion`: a copyright line that fades in
is theatre. The separator is decorative — the `<footer>` landmark
already tells AT where this region starts. -->
<Stack gap={6} marginTop={8}>
<Separator decorative />
<Flex
direction={{ base: 'column', sm: 'row' }}
justify="space-between"
align={{ base: 'start', sm: 'center' }}
gap={4}
>
{#if legal}
<Box>{@render legal()}</Box>
{/if}
{#if social || extra}
<Flex direction="row" align="center" gap={4}>
{#if social}
{@render social()}
{/if}
{#if extra}
{@render extra()}
{/if}
</Flex>
{/if}
</Flex>
</Stack>
{/if}
</Container>
</Section>
</footer>

Powered by TurnKey Linux.