Last time we looked at getting started with Qwik + Builder.io and now it is time to build something with it as a foundation. Onwidget has a nice template for Qwik { ❤️ dark mode } that we will be extending with Builder.io integration. Introducing QwikBuilder, a production ready starter template for building websites with Qwik + Builder.io 😍
Today, we will be creating navigation items to match the templates’ pages and register the hero component to be a cool cat 🙀 lets go!
Go to the Models section in Builder.io and create a ‘Top Menu’ Data model.
Add new ‘Item’ list type field with ‘Url’ as url and ‘Label’ as text for the list item fields. Go the Content section of Builder and add new ‘Top Menu’ and add Services, Portfolio, and About Us as items to match the templates’ pages and publish it. Now, we are ready to implement the code in Qwik
Note: example code at Builder.io does not work at at the time of writing as the SDK is evolving fast and we will need to make some changes
import { Resource, component$, useResource$, useStore } from '@builder.io/qwik';
import { useContent } from '@builder.io/qwik-city';
import { getAllContent } from '@builder.io/sdk-qwik';
export default component$(() => {
const linksResource = useResource$(() =>
getAllContent({
model: 'top-menu',
apiKey: process.env.BUILDER_PUBLIC_API_KEY as string,
})
);
interface MenuItem {
label: string;
url: string;
}
return (
<Resource
value={linksResource}
onPending={() => <>Loading...</>}
onRejected={(error) => <>Error: {error.message}</>}
// @ts-ignore FIX: after sdk is updated
onResolved={({ results }) => {
return (
// .. header component
<ul class="dropdown-menu rounded md:absolute pl-4 md:pl-0 md:hidden font-medium md:bg-white md:min-w-[200px] dark:md:bg-slate-800 drop-shadow-xl">
{results[0].data.item.map(
({ label, url }: MenuItem, index: number) => (
<li key={index}>
<a
class="font-medium rounded-t md:hover:bg-gray-100 dark:hover:bg-gray-700 py-2 px-4 block whitespace-no-wrap"
href={url}
>
{label}
</a>
</li>
)
)}
</ul>
)
}}
/>
);
});
Only showing the Builder.io bits here, refer to the repo for completed header component. Now, you can add more menu items to show up automatically in qwik app menu, remember to also create pages in Builder.
Custom components allow us to register our existing components and allow for selective editing like just the text copy or call-to-action. While rich text does allow to ‘edit as code’ for custom html/css, it will be erased if the built-in rich text editor is used to make edits to it.
Create a new ‘Homepage’ model in Builder and set Page URL as ‘/’. Register our hero component as custom component in Builder so we can make changes on the fly without the need to re-deploy
import { component$, Resource, useResource$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
import {
getContent,
RegisteredComponent,
RenderContent,
} from '@builder.io/sdk-qwik';
// TODO: add back when builder supports avif
// import srcsetAvif from '~/assets/images/hero.jpg?w=400;900&avif&srcset';
// @ts-ignore
import srcsetWebp from '~/assets/images/hero.jpg?w=400;900&webp&srcset';
// @ts-ignore
import { src as placeholder } from '~/assets/images/hero.jpg?width=400&metadata';
import { SITE } from '~/config.mjs';
export const apiKey = process.env.BUILDER_PUBLIC_API_KEY as string;
// let heroSrcsetAvif: string = srcsetAvif;
let heroSrcsetWebp: string = srcsetWebp;
interface HeroProps {
title1: string;
title2: string;
title3: string;
cta1Text: string;
cta1Link: string;
}
export const Hero = component$((props: HeroProps) => {
return (
<section
class={`bg-gradient-to-b md:bg-gradient-to-r from-white via-purple-50 to-sky-100 dark:bg-none`}
>
<div class="max-w-6xl mx-auto px-4 sm:px-6 md:flex md:h-screen 2xl:h-auto pt-[72px]">
<div class="py-12 md:py-12 lg:py-16 block md:flex text-center md:text-left">
<div class="pb-12 md:pb-0 md:py-0 mx-auto md:pr-16 flex items-center basis-3/5">
<div>
<h1 class="text-5xl md:text-[3.48rem] font-bold leading-tighter tracking-tighter mb-4 font-heading px-4 md:px-0">
{props.title1}
<br class="hidden lg:block" />{' '}
<span class="hidden lg:inline">{props.title2} </span>{' '}
<span dangerouslySetInnerHTML={props.title3} />
</h1>
<div class="max-w-3xl mx-auto">
<p class="text-xl text-gray-600 mb-8 dark:text-slate-400">
<span class="font-semibold underline decoration-wavy decoration-1 decoration-secondary-600 underline-offset-2">
{SITE.name}
</span>{' '}
is a production ready starter template for building websites
using <em>Qwik</em> + <em>Builder.io</em>. It has been
designed following Best Practices, SEO, Accessibility,{' '}
<span class="inline md:hidden">...</span>
<span class="hidden md:inline">
Dark Mode, Great Page Speed, image optimization, sitemap
generation and more.
</span>
</p>
<div class="max-w-xs sm:max-w-md flex flex-nowrap flex-col sm:flex-row gap-4 m-auto md:m-0 justify-center md:justify-start">
<div class="flex w-full sm:w-auto">
<a
class="btn btn-primary sm:mb-0 w-full"
href={props.cta1Link}
target="_blank"
rel="noopener"
>
{props.cta1Text}
</a>
</div>
<div class="flex w-full sm:w-auto">
<button class="btn w-full bg-gray-50 dark:bg-transparent">
Learn more
</button>
</div>
</div>
</div>
</div>
</div>
<div class="block md:flex items-center flex-1">
<div class="relative m-auto max-w-4xl md:max-w-sm">
<picture>
{/* <source srcSet={heroSrcsetAvif} type="image/avif" /> */}
<source srcSet={heroSrcsetWebp} type="image/webp" />
<img
src={placeholder}
width={1000}
height={1250}
class="mx-auto w-full rounded-md md:h-full drop-shadow-2xl bg-gray-400 dark:bg-slate-700"
alt="QwikBuilder Hero Image (Cool dog)"
loading="eager"
decoding="async"
/>
</picture>
</div>
</div>
</div>
</div>
</section>
);
});
export const CUSTOM_COMPONENTS: RegisteredComponent[] = [
{
component: Hero,
name: 'Hero',
noWrap: true,
hideFromInsertMenu: false,
inputs: [
{
name: 'title1',
type: 'string',
defaultValue: 'Starter template for',
},
{
name: 'title2',
type: 'string',
defaultValue: 'building websites with',
},
{
name: 'title3',
type: 'richText',
defaultValue: `<span class="text-[#039de1]">Qwik</span> +
<span class="sm:whitespace-nowrap text-[#039de1]">
Builder.io
</span>`,
},
{
name: 'cta1Text',
type: 'string',
defaultValue: 'Get template',
},
{
name: 'cta1Link',
type: 'url',
defaultValue: 'https://github.com/callit-today/qwik-builder',
},
],
},
];
export default component$(() => {
const { url } = useLocation();
const builderContent = useResource$(() =>
getContent({
model: 'homepage',
apiKey: apiKey,
userAttributes: { urlPath: url.pathname },
})
);
return (
<div>
<Resource
value={builderContent}
onPending={() => <>Loading...</>}
onRejected={(error) => <>Error: {error.message}</>}
onResolved={(content) => {
const heroImage =
// @ts-ignore
content?.data?.inputs[0]?.defaultValue?.split('?')[0];
if (heroImage) {
// heroSrcsetAvif = heroImage + '?format=avif&w=400;900&avif&srcset';
heroSrcsetWebp = heroImage + '?format=webp&w=400;900&webp&srcset';
}
return (
<RenderContent
model="homepage"
content={content}
apiKey={apiKey}
customComponents={CUSTOM_COMPONENTS}
/>
);
}}
/>
</div>
);
});
We are using props
to pass the default values from CUSTOM_COMPONETS
array to our Hero
component which is registered and now available in Builder. Go to contents and select the component under ‘Layers’ tab on the left and click on ‘Options’ tab to edit the custom fields.
Now, click on ‘Data’ tab on the right to add ‘Hero Image’ of type ‘File’ and upload an image. Remember to publish the homepage when done and visit http://localhost:5173 to see the changes
Finally, our hero component is a cool cat all thanks to custom components in Builder.io 😺
Look forward to seeing what you create over at twitter- @prasmalla and let me know what we should do next. Perhaps integrate e-commerce with Vendure 😉
Top comments (0)