feat: useLayoutWrapper

This commit is contained in:
2026-02-11 09:24:05 -05:00
parent 71a48de945
commit 3ec98fdc2d
17 changed files with 259 additions and 162 deletions

View File

@@ -0,0 +1,3 @@
fragment LayoutColored on GroupLayoutColored_Fields {
color
}

View File

@@ -1,5 +1,3 @@
fragment LayoutContained on GroupLayoutContained_Fields {
container
verticalPadding
bgColor
}

View File

@@ -1,50 +0,0 @@
<script setup lang="ts">
import type { LayoutContainedFragment } from "#graphql/operations";
import { tv, type VariantProps } from "tailwind-variants";
const props = defineProps<LayoutContainedFragment>();
const layoutWrapperVariants = tv({
slots: {
base: "",
inner: "",
},
variants: {
container: {
default: { inner: "container" },
lg: { inner: "container-lg" },
xl: { inner: "container-xl" },
fluid: { inner: "container-fluid" },
none: { inner: "container-none" },
},
verticalPadding: {
sm: { base: "py-3" },
md: { base: "py-6" },
lg: { base: "py-12" },
},
bgColor: {
default: { base: "bg-default" },
muted: { base: "bg-muted" },
inverted: { base: "bg-inverted text-inverted" },
},
},
defaultVariants: {
container: "default",
verticalPadding: "md",
bgColor: "default",
},
});
const { base, inner } = layoutWrapperVariants({
container: props.container[0],
verticalPadding: props.verticalPadding[0],
bgColor: props.bgColor[0],
} as VariantProps<typeof layoutWrapperVariants>);
</script>
<template>
<section :class="base()">
<div :class="inner()">
<slot />
</div>
</section>
</template>

View File

@@ -0,0 +1,3 @@
fragment LayoutPadded on GroupLayoutPadded_Fields {
verticalPadding
}

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
const props = defineProps<{ layoutSettings?: LayoutSettings }>();
const { base, inner } = useLayoutWrapper(props.layoutSettings);
</script>
<template>
<section :class="base()">
<div :class="inner()">
<slot />
</div>
</section>
</template>

View File

@@ -6,9 +6,7 @@ defineProps<NodePageFragment>();
<template>
<div id="node-page">
<h1 v-if="!isFrontPage" class="text-4xl font-bold">
{{ title }}
</h1>
<PageHeader v-if="!isFrontPage" :title="title"></PageHeader>
<BuilderSections :sections="groupPostPage?.sections || []" />
</div>
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
defineProps<{
title?: string;
}>();
</script>
<template>
<header v-if="title" class="bg-accented py-6">
<div class="container">
<h1 class="heading-1">
{{ title }}
</h1>
</div>
</header>
</template>

View File

@@ -1,18 +1,19 @@
<script setup lang="ts">
const { isLoggedIn } = useAuth();
const { isRedirecting } = useAuthConnexion();
const layoutSettings: LayoutWrapperProps = {
container: "lg",
};
</script>
<template>
<section data-section-name="auth-connexion" class="py-12">
<div class="container-sm">
<AuthState>
<AuthRedirecting v-if="isRedirecting" />
<template v-else>
<AuthLogoutForm v-if="isLoggedIn" />
<AuthLoginForm v-else />
</template>
</AuthState>
</div>
</section>
<LayoutWrapper data-section-name="auth-connexion" :layout-settings="layoutSettings">
<AuthState>
<AuthRedirecting v-if="isRedirecting" />
<template v-else>
<AuthLogoutForm v-if="isLoggedIn" />
<AuthLoginForm v-else />
</template>
</AuthState>
</LayoutWrapper>
</template>

View File

@@ -2,4 +2,8 @@ fragment SectionHeroSplit on GroupAbstractBuilderSectionsHeroSplitLayout {
content
reverse
...AcfMedia
layoutSettings {
...LayoutColored
...LayoutPadded
}
}

View File

@@ -3,8 +3,8 @@ import { tv, type VariantProps } from "tailwind-variants";
import type { SectionHeroSplitFragment } from "#graphql/operations";
const tvSectionHeroSplit = tv({
extend: tvLayoutWrapper,
slots: {
base: "py-6",
container: "container flex flex-col items-center gap-6",
content: "flex-1",
media: "w-full basis-1/2",
@@ -27,6 +27,7 @@ const tvSectionHeroSplit = tv({
const props = defineProps<SectionHeroSplitFragment>();
const classes = tvSectionHeroSplit({
reverse: props.reverse,
...props.layoutSettings,
} as VariantProps<typeof tvSectionHeroSplit>);
</script>

View File

@@ -1,6 +1,8 @@
fragment SectionTextBlock on GroupAbstractBuilderSectionsTextBlockLayout {
content
layoutSettings {
...LayoutColored
...LayoutContained
...LayoutPadded
}
}

View File

@@ -5,7 +5,7 @@ defineProps<SectionTextBlockFragment>();
</script>
<template>
<LayoutContained data-section-type="text-block" v-bind="layoutSettings!">
<LayoutWrapper data-section-type="text-block" :layout-settings="layoutSettings">
<UiProse :content="content" />
</LayoutContained>
</LayoutWrapper>
</template>

View File

@@ -0,0 +1,64 @@
import { tv, type VariantProps } from "tailwind-variants";
import * as z from "zod";
// Tailwind Variants for LayoutWrapper
export const tvLayoutWrapper = tv({
slots: {
base: "",
inner: "",
},
variants: {
// LayoutColored
color: {
default: { base: "bg-default" },
muted: { base: "bg-muted" },
elevated: { base: "bg-elevated" },
accented: { base: "bg-accented" },
inverted: { base: "bg-inverted text-inverted" },
primary: { base: "bg-primary text-inverted" },
},
// LayoutContained
container: {
default: { inner: "container" },
xl: { inner: "container-xl" },
lg: { inner: "container-lg" },
fluid: { inner: "container-fluid" },
fullbleed: { inner: "container-fullbleed" },
},
// LayoutPadded
verticalPadding: {
sm: { base: "py-3" },
md: { base: "py-6" },
lg: { base: "py-12" },
none: {},
},
},
defaultVariants: {
color: "default",
container: "default",
verticalPadding: "md",
},
});
export type LayoutWrapperProps = VariantProps<typeof tvLayoutWrapper>;
// Zod schemas for validating layout settings
const colorEnum = z.enum(["default", "muted", "elevated", "accented", "inverted", "primary"]);
const containerEnum = z.enum(["default", "xl", "lg", "fluid", "fullbleed"]);
const verticalPaddingEnum = z.enum(["sm", "md", "lg", "none"]);
const layoutSettingsSchema = z.object({
color: z.string().pipe(colorEnum).optional(),
container: z.string().pipe(containerEnum).optional(),
verticalPadding: z.string().pipe(verticalPaddingEnum).optional(),
});
export type LayoutSettings = z.input<typeof layoutSettingsSchema>;
export function useLayoutWrapper(input?: LayoutSettings) {
try {
return tvLayoutWrapper(layoutSettingsSchema.parse(input));
} catch {
return tvLayoutWrapper();
}
}