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' },