Skip to content

Commit 8b50b54

Browse files
committed
✨ Add Layout and SEO blocks
1 parent 640880a commit 8b50b54

19 files changed

+571
-119
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ import { Accordion } from 'webcoreui/react'
285285
- [GridWithIcons](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/GridWithIcons)
286286
- [Hero](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/Hero)
287287
- [IconList](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/IconList)
288+
- [Layout](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/Layout)
289+
- [SEO](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/SEO)
288290
- [SettingCard](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/SettingCard)
289291
- [SignUp](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/SignUp)
290292
- [SocialProof](https://github.com/Frontendland/webcoreui/tree/main/src/blocks/SocialProof)

src/blocks/Layout/Layout.astro

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
import { ConditionalWrapper, Footer, Menu } from 'webcoreui/astro'
3+
4+
import SEO from '@blocks/SEO/SEO.astro'
5+
6+
import type { LayoutProps } from './layout'
7+
import './layout.scss'
8+
9+
interface Props extends LayoutProps {}
10+
11+
const {
12+
seo,
13+
menu,
14+
footer,
15+
...rest
16+
} = Astro.props
17+
18+
const hasLeftSidebar = Astro.slots.has('left-sidebar')
19+
const hasRightSidebar = Astro.slots.has('right-sidebar')
20+
const hasSidebar = hasLeftSidebar || hasRightSidebar
21+
22+
const containerClasses = [
23+
'container',
24+
hasSidebar && 'flex column sm-row'
25+
]
26+
---
27+
28+
<!doctype html>
29+
<html lang="en">
30+
<head>
31+
<SEO {...seo} />
32+
</head>
33+
<body {...rest}>
34+
{menu && (
35+
<Menu {...menu}>
36+
<slot name="menu" />
37+
</Menu>
38+
)}
39+
<slot name="atf" />
40+
<main class:list={containerClasses}>
41+
<slot name="left-sidebar" />
42+
<ConditionalWrapper condition={hasSidebar}>
43+
<div slot="wrapper">children</div>
44+
<slot />
45+
</ConditionalWrapper>
46+
<slot name="right-sidebar" />
47+
</main>
48+
<slot name="above-footer" />
49+
{footer && (
50+
<Footer {...footer}>
51+
<slot name="footer" />
52+
</Footer>
53+
)}
54+
<slot name="scripts" />
55+
</body>
56+
</html>

src/blocks/Layout/Layout.svelte

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script lang="ts">
2+
import { classNames } from 'webcoreui'
3+
import { ConditionalWrapper, Footer, Menu } from 'webcoreui/svelte'
4+
5+
import SEO from '@blocks/SEO/SEO.svelte'
6+
7+
import type { SvelteLayoutProps } from './layout'
8+
import './layout.scss'
9+
10+
const {
11+
seo,
12+
menu,
13+
footer,
14+
insideMenu,
15+
atf,
16+
leftSidebar,
17+
rightSidebar,
18+
aboveFooter,
19+
insideFooter,
20+
scripts,
21+
children,
22+
...rest
23+
}: SvelteLayoutProps = $props()
24+
25+
const hasSidebar = leftSidebar || rightSidebar
26+
27+
const containerClasses = classNames([
28+
'container',
29+
hasSidebar && 'flex column sm-row'
30+
])
31+
</script>
32+
33+
<html lang="en">
34+
<head>
35+
<SEO {...seo} />
36+
</head>
37+
<body {...rest}>
38+
{#if menu}
39+
<Menu {...menu}>
40+
{@render insideMenu?.()}
41+
</Menu>
42+
{/if}
43+
{@render atf?.()}
44+
<main class={containerClasses}>
45+
{@render leftSidebar?.()}
46+
<ConditionalWrapper condition={!!hasSidebar}>
47+
{@render children?.()}
48+
</ConditionalWrapper>
49+
{@render rightSidebar?.()}
50+
</main>
51+
{@render aboveFooter?.()}
52+
{#if footer}
53+
<Footer {...footer}>
54+
{@render insideFooter?.()}
55+
</Footer>
56+
{/if}
57+
{@render scripts?.()}
58+
</body>
59+
</html>

src/blocks/Layout/Layout.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react'
2+
import { classNames } from 'webcoreui'
3+
import { ConditionalWrapper, Footer, Menu } from 'webcoreui/react'
4+
5+
import SEO from '@blocks/SEO/SEO.tsx'
6+
7+
import type { ReactLayoutProps } from './layout'
8+
import './layout.scss'
9+
10+
const Layout = ({
11+
seo,
12+
menu,
13+
footer,
14+
insideMenu,
15+
atf,
16+
leftSidebar,
17+
rightSidebar,
18+
aboveFooter,
19+
insideFooter,
20+
scripts,
21+
children,
22+
...rest
23+
}: ReactLayoutProps) => {
24+
const hasSidebar = leftSidebar || rightSidebar
25+
26+
const containerClasses = classNames([
27+
'container',
28+
hasSidebar && 'flex column sm-row'
29+
])
30+
31+
return (
32+
<html lang="en">
33+
<head>
34+
<SEO {...seo} />
35+
</head>
36+
<body {...rest}>
37+
{menu && (
38+
<Menu {...menu}>
39+
{insideMenu || <span />}
40+
</Menu>
41+
)}
42+
{atf}
43+
<main className={containerClasses}>
44+
{leftSidebar}
45+
<ConditionalWrapper
46+
condition={!!hasSidebar}
47+
wrapper={children => <div>{children}</div>}
48+
>
49+
{children}
50+
</ConditionalWrapper>
51+
{rightSidebar}
52+
</main>
53+
{aboveFooter}
54+
{footer && (
55+
<Footer {...footer}>
56+
{insideFooter}
57+
</Footer>
58+
)}
59+
{scripts}
60+
</body>
61+
</html>
62+
)
63+
}
64+
65+
export default Layout

src/blocks/Layout/layout.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@use 'webcoreui/styles' as *;
2+
@use 'webcoreui/config' as *;
3+
@include setup((
4+
fontRegular: '/fonts/Inter-Regular.woff2',
5+
fontBold: '/fonts/Inter-Bold.woff2'
6+
));
7+
8+
.container {
9+
@include spacing(auto-default);
10+
}

src/blocks/Layout/layout.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Snippet } from 'svelte'
2+
import type { FooterProps, MenuProps } from 'webcoreui/astro'
3+
4+
import type { SEOProps } from '@blocks/SEO/SEO.ts'
5+
6+
export type LayoutProps = {
7+
seo: SEOProps
8+
menu?: MenuProps
9+
footer?: FooterProps
10+
[key: string]: any
11+
}
12+
13+
export type SvelteLayoutProps = {
14+
insideMenu?: Snippet
15+
atf?: Snippet
16+
leftSidebar?: Snippet
17+
rightSidebar?: Snippet
18+
aboveFooter?: Snippet
19+
insideFooter?: Snippet
20+
scripts?: Snippet
21+
children?: Snippet
22+
} & LayoutProps
23+
24+
export type ReactLayoutProps = {
25+
insideMenu?: React.ReactNode
26+
atf?: React.ReactNode
27+
leftSidebar?: React.ReactNode
28+
rightSidebar?: React.ReactNode
29+
aboveFooter?: React.ReactNode
30+
insideFooter?: React.ReactNode
31+
scripts?: React.ReactNode
32+
children?: React.ReactNode
33+
} & LayoutProps

src/blocks/SEO/SEO.astro

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,61 @@
11
---
2-
import {
3-
Card,
4-
Progress
5-
} from 'webcoreui/astro'
2+
import type { SEOProps } from './SEO'
63
7-
import styles from './seo.module.scss'
4+
interface Props extends SEOProps {}
85
9-
const progressClass = [
10-
'flex justify-between muted',
11-
styles['progress-label']
12-
]
6+
const {
7+
title,
8+
url,
9+
faviconUrl,
10+
description,
11+
canonical,
12+
prefetchGTAG,
13+
prefetchGA,
14+
noIndex,
15+
meta,
16+
hrefLangs,
17+
structuredContents
18+
} = Astro.props
1319
---
1420

15-
<Card title="SEO Overview">
16-
<span>Keep track of the SEO performance of your posts.</span>
17-
18-
<div class:list={['flex column md', styles.my]}>
19-
<div>
20-
<div class:list={progressClass}>
21-
<span>Underperforming</span>
22-
<span>50%</span>
23-
</div>
24-
<Progress value={50} color="var(--w-color-alert)" />
25-
</div>
26-
<div>
27-
<div class:list={progressClass}>
28-
<span>OK</span>
29-
<span>30%</span>
30-
</div>
31-
<Progress value={30} color="var(--w-color-warning)" />
32-
</div>
33-
<div>
34-
<div class:list={progressClass}>
35-
<span>SEO-friendly</span>
36-
<span>20%</span>
37-
</div>
38-
<Progress value={20} color="var(--w-color-success)" />
39-
</div>
40-
</div>
41-
</Card>
21+
<title>{title}</title>
22+
23+
{prefetchGTAG && (
24+
<link rel="dns-prefetch" href="https://www.googletagmanager.com/" />
25+
)}
26+
27+
{prefetchGA && (
28+
<link rel="dns-prefetch" href="https://www.google-analytics.com/" />
29+
)}
30+
31+
<link rel="icon" type="image/x-icon" href={faviconUrl} />
32+
<link rel="canonical" href={canonical || url} />
33+
34+
{hrefLangs?.map(hrefLang => (
35+
<link rel="alternate" hreflang={hrefLang.hreflang} href={hrefLang.href} />
36+
))}
37+
38+
<meta charset="utf-8" />
39+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
40+
<meta name="description" content={description} />
41+
<meta name="robots" content={noIndex ? 'noindex, nofollow' : 'index, follow'} />
42+
<meta name="og:url" content={url} />
43+
<meta name="og:title" content={title} />
44+
<meta name="og:description" content={description} />
45+
<meta name="og:type" content="website" />
46+
<meta name="twitter:card" content="summary" />
47+
<meta name="twitter:title" content={title} />
48+
<meta name="twitter:description" content={description} />
49+
50+
{meta?.map(meta => (
51+
<meta name={meta.name} content={meta.content} />
52+
))}
53+
54+
{structuredContents?.filter(Boolean).map(structuredContent => (
55+
<script
56+
is:inline
57+
type="application/ld+json"
58+
data-id={import.meta.env.DEV ? `${structuredContent['@type']}Schema` : null}
59+
set:html={JSON.stringify(structuredContent, null, import.meta.env.DEV ? 4 : 0)}
60+
/>
61+
))}

0 commit comments

Comments
 (0)