diff --git a/src/components/BackendGuidesNav.astro b/src/components/BackendGuidesNav.astro
index 71fa23f3e148d..5faea7cd83fcf 100644
--- a/src/components/BackendGuidesNav.astro
+++ b/src/components/BackendGuidesNav.astro
@@ -16,11 +16,11 @@ const links = enPages
.map((page) => {
const { service } = page.data;
const pageUrl = '/' + page.id.replace('en/', `${lang}/`) + '/';
- const logo = isLogoKey(page.id.split('/').pop());
+ const logo = { brand: isLogoKey(page.id.split('/').pop()) };
return { title: service, href: pageUrl, logo };
});
---
diff --git a/src/components/BrandLogo.astro b/src/components/BrandLogo.astro
index 2c063a99caeaf..941828d8e0695 100644
--- a/src/components/BrandLogo.astro
+++ b/src/components/BrandLogo.astro
@@ -2,31 +2,26 @@
import { type LogoKey, logos } from '~/data/logos';
export interface Props {
- size?: `${number}rem` | `${number}px`;
+ size?: 'large' | 'default';
shape?: 'circle' | 'rounded';
brand: LogoKey;
}
-const { brand, size = '4rem', shape = 'circle' } = Astro.props as Props;
-const { file, padding } = logos[brand] || {};
-
-// Make a rough guess at the pixel size to use as width/height attributes
-const [, value, unit] = /^(\d*(?:\.\d+)?)(\w+)$/.exec(size) || ['4', 'rem'];
-const valueAsNumber = parseFloat(value);
-const pixelSize = unit === 'px' ? valueAsNumber : valueAsNumber * 16;
+const { brand, size = 'default', shape = 'circle' } = Astro.props;
+const { file, padding, bg } = logos[brand] || {};
---
{
file && (
-
-

+
+
)
}
@@ -39,8 +34,8 @@ const pixelSize = unit === 'px' ? valueAsNumber : valueAsNumber * 16;
width: 1em;
height: 1em;
/* Allows logos to be visible in both light/dark. Hardcoded because variant colours adjust to theme */
- background-color: hsl(224, 10%, 10%);
- border: 1px solid hsl(224, 10%, 23%);
+ background-color: var(--logo-bg, hsl(224, 10%, 10%));
+ border: 1px solid hsla(0, 0%, 100%, 0.1);
/* Indicates the brand logo boundaries for forced colors users, transparent is changed to a solid color */
outline: 1px solid transparent;
overflow: hidden;
diff --git a/src/components/CMSGuidesNav.astro b/src/components/CMSGuidesNav.astro
index 70f5afaa586f4..f8c8e9ff4d727 100644
--- a/src/components/CMSGuidesNav.astro
+++ b/src/components/CMSGuidesNav.astro
@@ -16,11 +16,11 @@ const links = enPages
.map((page) => {
const { service } = page.data;
const pageUrl = '/' + page.id.replace('en/', `${lang}/`) + '/';
- const logo = isLogoKey(page.id.split('/').pop());
+ const logo = { brand: isLogoKey(page.id.split('/').pop()) };
return { title: service, href: pageUrl, logo };
});
---
diff --git a/src/components/DeployGuidesNav.astro b/src/components/DeployGuidesNav.astro
index c85d33fb5c7ed..1ed9deca12e04 100644
--- a/src/components/DeployGuidesNav.astro
+++ b/src/components/DeployGuidesNav.astro
@@ -48,11 +48,11 @@ const services: Service[] = [
---
({
title,
href: `/${lang}/guides/deploy/${slug}/`,
- logo: slug,
+ logo: { brand: slug },
tags: Object.fromEntries(supports.map((s) => [s, Astro.locals.t(`deploy.${s}Tag`)!])),
}))}
/>
diff --git a/src/components/FeaturedCMSGuidesNav.astro b/src/components/FeaturedCMSGuidesNav.astro
new file mode 100644
index 0000000000000..238699e1107d7
--- /dev/null
+++ b/src/components/FeaturedCMSGuidesNav.astro
@@ -0,0 +1,26 @@
+---
+import { allPages } from '~/content';
+import { isCmsEntry, isEnglishEntry } from '~/content.config';
+import { isLogoKey } from '~/data/logos';
+import { getLanguageFromURL, stripLangFromSlug } from '~/util/path-utils';
+import CardsNav from './NavGrid/CardsNav.astro';
+
+const lang = getLanguageFromURL(Astro.url.pathname);
+const featuredCmsGuides = allPages.filter(isCmsEntry).filter((entry) => entry.data.featuredListing);
+const enFeaturedCmsGuides = featuredCmsGuides.filter(isEnglishEntry);
+---
+
+ {
+ const slugWithoutLang = stripLangFromSlug(id);
+ const localizedSlug = `${lang}/${slugWithoutLang}`;
+ const translation = featuredCmsGuides.find((page) => localizedSlug === page.id);
+ return {
+ title: translation?.data.service || data.service,
+ href: `/${localizedSlug}/`,
+ logo: { brand: isLogoKey(id.split('/').pop()), size: 'large', shape: 'rounded' },
+ description: translation?.data.featuredListing?.tagline || data.featuredListing!.tagline,
+ };
+ })}
+/>
diff --git a/src/components/IntegrationsNav.astro b/src/components/IntegrationsNav.astro
index e5cb142344ac3..77ebcee4b9e7a 100644
--- a/src/components/IntegrationsNav.astro
+++ b/src/components/IntegrationsNav.astro
@@ -28,7 +28,7 @@ function categoryLinksFromPages(pages: IntegrationEntry[], category: Integration
'/' +
name.replaceAll('-', '-'),
href: pageUrl,
- logo: isLogoKey(name),
+ logo: { brand: isLogoKey(name) },
};
});
}
@@ -58,7 +58,7 @@ const categories = category ? [category] : allCategories;
Object.values(categories).map((category) => (
<>
{category.title}
-
+
>
))
}
diff --git a/src/components/MediaGuidesNav.astro b/src/components/MediaGuidesNav.astro
index c65f0b63df8da..63294caa42c7d 100644
--- a/src/components/MediaGuidesNav.astro
+++ b/src/components/MediaGuidesNav.astro
@@ -16,11 +16,11 @@ const links = enPages
.map((page) => {
const { service } = page.data;
const pageUrl = '/' + page.id.replace('en/', `${lang}/`) + '/';
- const logo = isLogoKey(page.id.split('/').pop());
+ const logo = { brand: isLogoKey(page.id.split('/').pop()) };
return { title: service, href: pageUrl, logo };
});
---
diff --git a/src/components/MigrationGuidesNav.astro b/src/components/MigrationGuidesNav.astro
index 6f5da11ac8c8b..a363a68970c29 100644
--- a/src/components/MigrationGuidesNav.astro
+++ b/src/components/MigrationGuidesNav.astro
@@ -17,10 +17,10 @@ const links = enPages
.map((page) => {
const pageUrl = page.id.replace('en/', `/${lang}/`) + '/';
const slug = page.id.split('/').pop()?.replace('from-', '');
- return { title: page.data.framework, href: pageUrl, logo: isLogoKey(slug) };
+ return { title: page.data.framework, href: pageUrl, logo: { brand: isLogoKey(slug) } };
});
---
diff --git a/src/components/NavGrid/Card.astro b/src/components/NavGrid/Card.astro
index 9c02277463e01..33c0fcaccd688 100644
--- a/src/components/NavGrid/Card.astro
+++ b/src/components/NavGrid/Card.astro
@@ -1,27 +1,29 @@
---
-import type { LogoKey } from '~/data/logos';
+import type { ComponentProps, HTMLAttributes } from 'astro/types';
import BrandLogo from '../BrandLogo.astro';
-import type { HTMLAttributes } from 'astro/types';
export interface Props extends HTMLAttributes<'li'> {
href: string;
- logo?: LogoKey;
+ logo?: ComponentProps;
current?: boolean;
- minimal?: boolean;
+ size?: 'small' | 'default' | 'large';
}
-const { href, logo, current, minimal, class: classes, ...attrs } = Astro.props as Props;
+const { href, logo, current, size = 'default', class: classes, ...attrs } = Astro.props;
---
-
- {logo && }
+
+ {logo && }
- {!minimal &&
}
+ {size !== 'small' &&
}
@@ -36,11 +38,19 @@ const { href, logo, current, minimal, class: classes, ...attrs } = Astro.props a
align-items: center;
border-radius: 0.5rem;
}
- .card--minimal {
+ .card--sm {
grid-template-columns: 1fr;
justify-items: center;
text-align: center;
}
+ .card--lg {
+ gap: 1rem;
+ align-items: flex-start;
+ line-height: 1.5;
+ }
+ .card--lg .stack {
+ gap: 0.25rem;
+ }
.card:hover,
.card:focus-within {
@@ -65,7 +75,7 @@ const { href, logo, current, minimal, class: classes, ...attrs } = Astro.props a
font-weight: 600;
}
- .card--minimal h3 {
+ .card--sm h3 {
font-size: var(--sl-text-body);
}
diff --git a/src/components/NavGrid/CardsNav.astro b/src/components/NavGrid/CardsNav.astro
index 74b0906d81cec..66366b5e3337c 100644
--- a/src/components/NavGrid/CardsNav.astro
+++ b/src/components/NavGrid/CardsNav.astro
@@ -1,16 +1,16 @@
---
-import type { LogoKey } from '~/data/logos';
+import type { ComponentProps } from 'astro/types';
+import Badge from '~/components/Badge.astro';
import Card from './Card.astro';
import Grid from './Grid.astro';
-import Badge from '~/components/Badge.astro';
export interface Props {
- minimal?: boolean;
+ size?: 'small' | 'default' | 'large';
links: {
title: string;
description?: string;
href: string;
- logo?: LogoKey;
+ logo?: ComponentProps['logo'];
/** Map of tag IDs to translated tag display text, e.g. `{ static: 'Statisch' }`. */
tags?: Record;
/** The language of the content if it differs from the main page language. */
@@ -19,18 +19,18 @@ export interface Props {
class?: string;
}
-const { links, minimal = false, class: classes } = Astro.props as Props;
+const { links, size, class: classes } = Astro.props as Props;
const currentPage = new URL(Astro.request.url).pathname;
---
-
+
{
links.map(({ description, href, logo, title, tags, lang }) => (
@@ -55,6 +55,7 @@ const currentPage = new URL(Astro.request.url).pathname;
.description {
color: var(--sl-color-gray-2);
font-size: var(--sl-text-body-sm);
+ text-wrap: pretty;
}
.tags {
diff --git a/src/components/NavGrid/Grid.astro b/src/components/NavGrid/Grid.astro
index 83e4eff1cb8a4..38bf7aedfc749 100644
--- a/src/components/NavGrid/Grid.astro
+++ b/src/components/NavGrid/Grid.astro
@@ -1,12 +1,17 @@
---
export interface Props {
- minimal?: boolean;
+ size?: 'small' | 'default' | 'large';
}
-const { minimal } = Astro.props as Props;
+const { size } = Astro.props as Props;
---
-
+
@@ -30,8 +35,11 @@ const { minimal } = Astro.props as Props;
}
}
- .fluid-grid--minimal {
+ .fluid-grid--sm {
--column-min-width: 8rem;
gap: 1.5rem 0.75rem;
}
+ .fluid-grid--lg {
+ --column-min-width: 18rem;
+ }
diff --git a/src/components/starlight/MarkdownContent.astro b/src/components/starlight/MarkdownContent.astro
index d06e46a1c04a7..1f3c004c960af 100644
--- a/src/components/starlight/MarkdownContent.astro
+++ b/src/components/starlight/MarkdownContent.astro
@@ -1,12 +1,13 @@
---
import '@astrojs/starlight/style/markdown.css';
+import { getPageCategory } from '~/util/getPageCategory';
import BackendGuidesNav from '../BackendGuidesNav.astro';
import CMSGuidesNav from '../CMSGuidesNav.astro';
import DeployGuidesNav from '../DeployGuidesNav.astro';
-import MediaGuidesNav from '../MediaGuidesNav.astro';
+import FeaturedCMSGuidesNav from '../FeaturedCMSGuidesNav.astro';
import IntegrationsNav from '../IntegrationsNav.astro';
+import MediaGuidesNav from '../MediaGuidesNav.astro';
import MigrationGuidesNav from '../MigrationGuidesNav.astro';
-import { getPageCategory } from '~/util/getPageCategory';
const { entry } = Astro.locals.starlightRoute;
---
@@ -18,7 +19,7 @@ const { entry } = Astro.locals.starlightRoute;
entry.data.type === 'backend' && (
<>
{Astro.locals.t('backend.navTitle')}
-
+
>
)
}
@@ -26,7 +27,10 @@ const { entry } = Astro.locals.starlightRoute;
entry.data.type === 'cms' && (
<>
{Astro.locals.t('cms.navTitle')}
-
+ {Astro.locals.t('cms.featuredSubheading')}
+
+ {Astro.locals.t('cms.allSubheading')}
+
>
)
}
@@ -34,7 +38,7 @@ const { entry } = Astro.locals.starlightRoute;
entry.data.type === 'deploy' && (
<>
{Astro.locals.t('deploy.altSectionTitle')}
-
+
>
)
}
@@ -42,7 +46,7 @@ const { entry } = Astro.locals.starlightRoute;
entry.data.type === 'media' && (
<>
{Astro.locals.t('media.navTitle')}
-
+
>
)
}
@@ -58,7 +62,7 @@ const { entry } = Astro.locals.starlightRoute;
entry.data.type === 'migration' && (
<>
{Astro.locals.t('migration.navTitle')}
-
+
>
)
}
diff --git a/src/content.config.ts b/src/content.config.ts
index f183029f1b686..da154704a4994 100644
--- a/src/content.config.ts
+++ b/src/content.config.ts
@@ -34,6 +34,11 @@ export const cmsSchema = baseSchema.extend({
type: z.literal('cms'),
stub: z.boolean().default(false),
service: z.string(),
+ featuredListing: z
+ .object({
+ tagline: z.string().min(30).max(160),
+ })
+ .optional(),
});
export const integrationSchema = baseSchema.extend({
diff --git a/src/content/docs/en/guides/cms/cloudcannon.mdx b/src/content/docs/en/guides/cms/cloudcannon.mdx
index 7ed6bd1f52997..0a53358cc2ce6 100644
--- a/src/content/docs/en/guides/cms/cloudcannon.mdx
+++ b/src/content/docs/en/guides/cms/cloudcannon.mdx
@@ -7,6 +7,8 @@ type: cms
stub: true
service: CloudCannon
i18nReady: true
+featuredListing:
+ tagline: Git-based CMS built for speed, security, and zero headaches.
---
import Grid from '~/components/FluidGrid.astro'
diff --git a/src/content/docs/en/guides/cms/index.mdx b/src/content/docs/en/guides/cms/index.mdx
index 7857221fadf26..24bb3c33d3250 100644
--- a/src/content/docs/en/guides/cms/index.mdx
+++ b/src/content/docs/en/guides/cms/index.mdx
@@ -6,6 +6,7 @@ sidebar:
i18nReady: true
---
import CMSGuidesNav from '~/components/CMSGuidesNav.astro';
+import FeaturedCMSGuidesNav from '~/components/FeaturedCMSGuidesNav.astro';
import ReadMore from '~/components/ReadMore.astro';
import Badge from "~/components/Badge.astro"
@@ -15,7 +16,11 @@ import Badge from "~/components/Badge.astro"
Find [community-maintained integrations](https://astro.build/integrations/?search=cms) for connecting a CMS to your project in our integrations directory.
:::
-## CMS Guides
+## Featured CMS partners
+
+
+
+## CMS guides
Note that many of these pages are **stubs**: they're collections of resources waiting for your contribution!
diff --git a/src/content/i18n-schema.ts b/src/content/i18n-schema.ts
index e116ed1da3732..eb645fe5b4b49 100644
--- a/src/content/i18n-schema.ts
+++ b/src/content/i18n-schema.ts
@@ -25,6 +25,8 @@ export const AstroDocsI18nSchema = z
'deploy.staticTag': z.string(),
// CMS Guides vocabulary
'cms.navTitle': z.string(),
+ 'cms.featuredSubheading': z.string(),
+ 'cms.allSubheading': z.string(),
// Media Guides vocabulary
'media.navTitle': z.string(),
// Migration Guides vocabulary
diff --git a/src/content/i18n/en.yml b/src/content/i18n/en.yml
index 5d6556fa10c56..e479d8faee0ca 100644
--- a/src/content/i18n/en.yml
+++ b/src/content/i18n/en.yml
@@ -21,6 +21,8 @@ deploy.ssrTag: On demand
deploy.staticTag: Static
# CMS Guides vocabulary
cms.navTitle: More CMS guides
+cms.featuredSubheading: Featured CMS partners
+cms.allSubheading: All CMS guides
# Media Guides vocabulary
media.navTitle: More hosted media guides
# Migration Guides vocabulary
diff --git a/src/data/logos.ts b/src/data/logos.ts
index 7fda35ff56ea7..c2b4a338663c6 100644
--- a/src/data/logos.ts
+++ b/src/data/logos.ts
@@ -1,7 +1,16 @@
import { z } from 'astro:content';
+interface Logo {
+ /** Filename for this logo in `public/logos/*`, e.g. `"alpine-js.svg"`. */
+ file: string;
+ /** CSS padding in `em` units to apply around the logo, e.g. `"0.1em"` or `"0.1em 0.2em"`. */
+ padding?: string;
+ /** Accent color to use as the logo background in large applications. */
+ bg?: string;
+}
+
/** Enforce logo types while preserving exact key type. */
-const LogoCheck = >(logos: T) => logos;
+const LogoCheck = (logos: Record) => logos;
export const logos = LogoCheck({
alpinejs: { file: 'alpine-js.svg', padding: '.1875em' },
@@ -10,18 +19,18 @@ export const logos = LogoCheck({
cleavr: { file: 'cleavr.svg', padding: '0.125em 0.125em 0.1375em' },
cloudflare: { file: 'cloudflare-pages.svg', padding: '.1875em' },
cloudinary: { file: 'cloudinary.svg', padding: '.1875em' },
- cloudray: { file: 'cloudray.svg', padding: '0' },
+ cloudray: { file: 'cloudray.svg' },
'craft-cms': { file: 'craft-cms.svg', padding: '.225em' },
crystallize: { file: 'crystallize.svg', padding: '.1875em' },
'create-react-app': { file: 'create-react-app.svg', padding: '.1875em' },
datocms: { file: 'datocms.svg', padding: '0.25em 0.25em 0.25em 0.3em' },
- deno: { file: 'deno.svg', padding: '0' },
+ deno: { file: 'deno.svg' },
fleek: { file: 'fleek.svg', padding: '0.1000em' },
flotiq: { file: 'flotiq.svg', padding: '.05em' },
flyio: { file: 'flyio.svg', padding: '.1625em' },
gitcms: { file: 'gitcms.svg', padding: '0.20em' },
github: { file: 'github.svg', padding: '0.125em 0.125em 0.1375em' },
- gitlab: { file: 'gitlab.svg', padding: '0' },
+ gitlab: { file: 'gitlab.svg' },
'google-cloud': { file: 'google-cloud.svg', padding: '.1875em' },
'google-firebase': { file: 'firebase.svg', padding: '.1875em' },
hashnode: { file: 'hashnode.png', padding: '.1875em' },
@@ -29,7 +38,7 @@ export const logos = LogoCheck({
'microsoft-azure': { file: 'microsoft-azure.svg', padding: '.1625em .1625em .2125em' },
netlify: { file: 'netlify.svg', padding: '.1625em' },
render: { file: 'render.svg', padding: '.1875em' },
- stormkit: { file: 'stormkit.svg', padding: '0' },
+ stormkit: { file: 'stormkit.svg' },
surge: { file: 'surge.svg', padding: '.125em' },
vercel: { file: 'vercel.svg', padding: '.3em .3em .35em' },
image: { file: 'astro-image.svg', padding: '.1875em' },
@@ -62,14 +71,14 @@ export const logos = LogoCheck({
payload: { file: 'payload.svg', padding: '.3em .25em .3em .3em' },
prismic: { file: 'prismic.svg', padding: '.25em' },
caisy: { file: 'caisy.svg', padding: '.05em' },
- sanity: { file: 'sanity.svg', padding: '.15em' },
+ sanity: { file: 'sanity.svg', padding: '.15em', bg: '#F03E2F' },
sitecore: { file: 'sitecore.svg', padding: '.15em' },
sitepins: { file: 'sitepins.svg', padding: '.15em .15em' },
spinal: { file: 'spinal.svg', padding: '.15em .15em' },
storyblok: { file: 'storyblok.svg', padding: '.3em .25em .25em' },
wordpress: { file: 'wordpress.svg', padding: '.2em' },
- kinsta: { file: 'kinsta.svg', padding: '0' },
- gatsby: { file: 'gatsby.svg', padding: '0' },
+ kinsta: { file: 'kinsta.svg' },
+ gatsby: { file: 'gatsby.svg' },
nextjs: { file: 'nextjs.svg', padding: '.125em' },
jekyll: { file: 'jekyll.png', padding: '.1em .05em 0' },
hugo: { file: 'hugo.svg', padding: '.125em' },
@@ -84,7 +93,7 @@ export const logos = LogoCheck({
appwriteio: { file: 'appwriteio.svg', padding: '.2em' },
supabase: { file: 'supabase.svg', padding: '.2em' },
turso: { file: 'turso.svg', padding: '.2em' },
- cloudcannon: { file: 'cloudcannon.svg', padding: '.25em' },
+ cloudcannon: { file: 'cloudcannon.svg', padding: '.25em', bg: '#034ad8' },
markdoc: { file: 'markdoc.svg', padding: '.35em 0 .35em .1em' },
gitbook: { file: 'gitbook.svg', padding: '.25em' },
'frontmatter-cms': { file: 'frontmatter-cms.svg', padding: '.25em' },
@@ -92,13 +101,13 @@ export const logos = LogoCheck({
xata: { file: 'xata.svg', padding: '0.234em 0.234em 0.1875em' },
strapi: { file: 'strapi.svg', padding: '.25em' },
microcms: { file: 'microcms.svg', padding: '.2em' },
- preprcms: { file: 'preprcms.svg', padding: '0' },
+ preprcms: { file: 'preprcms.svg' },
'prisma-postgres': { file: 'prisma-postgres.svg', padding: '.20em' },
'kontent-ai': { file: 'kontent-ai.svg', padding: '.15em' },
- keystatic: { file: 'keystatic.svg', padding: '0' },
+ keystatic: { file: 'keystatic.svg' },
zeabur: { file: 'zeabur.svg', padding: '.2em' },
zerops: { file: 'zerops.svg', padding: '.2em' },
- apostrophecms: { file: 'apostrophecms.svg', padding: '.15em .15em' },
+ apostrophecms: { file: 'apostrophecms.svg', padding: '.15em' },
db: { file: 'db.svg', padding: '.1em' },
sentry: { file: 'sentry.svg', padding: '.1em' },
umbraco: { file: 'umbraco.svg', padding: '.05em' },