diff --git a/.env b/.env index f5bafb4c..28f19231 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ -NEXT_PUBLIC_CLOUDSMITH_API_URL="https://api.cloudsmith.io/v2" +NEXT_PUBLIC_CLOUDSMITH_API_URL="https://api.cloudsmith.io" +NEXT_PUBLIC_CLOUDSMITH_DOCS_URL="https://github.com/cloudsmith-io/cloudsmith-docs" +NEXT_PUBLIC_CLOUDSMITH_DOCS_BRANCH="main" CLOUDSMITH_API_V1_URL="https://api.cloudsmith.io/swagger/?format=openapi" CLOUDSMITH_API_V2_URL="https://api.cloudsmith.io/v2/openapi/?format=json" \ No newline at end of file diff --git a/README.md b/README.md index da822ff0..821a2326 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Then refer to the following guides on how to manage the content of the website: - [Writing markdown](./docs/markdown.md) - [Using snippets](./docs/snippets.md) - [Editing menus](./docs/menus.md) +- [OopenAPI Schemas](./docs/openapi.md) ## Getting started diff --git a/docs/markdown.md b/docs/markdown.md index 9cbd7995..b72aba9d 100644 --- a/docs/markdown.md +++ b/docs/markdown.md @@ -69,7 +69,7 @@ In the Guides section, you can use a special `GuideLink` component to show a sol ```mdx import { GuideLink } from '@/components'; -Click me +Click me ``` ## Dividers diff --git a/docs/openapi.md b/docs/openapi.md new file mode 100644 index 00000000..d5ee0d08 --- /dev/null +++ b/docs/openapi.md @@ -0,0 +1,8 @@ +# OpenAPI Schemas + +The `/api` pages are automatically generated based on two OpenAPI files in `src/content/schemas` holding the `v1` and `v2` API paths. + +There are two special features: + +- If a path has `"experimental"` as a tag, it will be listed with a big noted saying that this API endpoint is in early access. +- If a path has a `sandboxLink` property with a fully qualified URL, the page will show a API Sandbox link to that URL. diff --git a/package-lock.json b/package-lock.json index 2b406ba9..032f543b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3792,19 +3792,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af8a426c..d568cb02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1256,14 +1256,14 @@ packages: resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.10.0': - resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.11.0': resolution: {integrity: sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.2.0': resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1276,8 +1276,8 @@ packages: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.5': - resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@humanfs/core@0.19.1': @@ -6537,11 +6537,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/core@0.10.0': + '@eslint/core@0.11.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@0.11.0': + '@eslint/core@0.13.0': dependencies: '@types/json-schema': 7.0.15 @@ -6563,9 +6563,9 @@ snapshots: '@eslint/object-schema@2.1.4': {} - '@eslint/plugin-kit@0.2.5': + '@eslint/plugin-kit@0.2.8': dependencies: - '@eslint/core': 0.10.0 + '@eslint/core': 0.13.0 levn: 0.4.1 '@humanfs/core@0.19.1': {} @@ -8559,7 +8559,7 @@ snapshots: '@eslint/core': 0.11.0 '@eslint/eslintrc': 3.2.0 '@eslint/js': 9.20.0 - '@eslint/plugin-kit': 0.2.5 + '@eslint/plugin-kit': 0.2.8 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.1 diff --git a/src/app/(api)/Sidebar.tsx b/src/app/(api)/Sidebar.tsx index 4e1c621e..704dd3ce 100644 --- a/src/app/(api)/Sidebar.tsx +++ b/src/app/(api)/Sidebar.tsx @@ -1,12 +1,12 @@ import { getMenuItem } from '@/lib/menu/util'; import { Sidenav } from '@/components'; -import { parseSchema, toMenuItems, toOperations } from '@/lib/swagger/parse'; +import { parseSchemas, toMenuItems, toOperations } from '@/lib/swagger/parse'; export const Sidebar = async () => { const menuData = getMenuItem('api'); - const schema = await parseSchema(); - const operations = toOperations(schema); + const schemas = await parseSchemas(); + const operations = toOperations(schemas); const menuItems = toMenuItems(operations); const allItems = []; diff --git a/src/app/(api)/api/[...slug]/page.module.css b/src/app/(api)/api/[...slug]/page.module.css index 98808eba..62814466 100644 --- a/src/app/(api)/api/[...slug]/page.module.css +++ b/src/app/(api)/api/[...slug]/page.module.css @@ -10,6 +10,38 @@ grid-template-columns: minmax(100px, min-content) minmax(150px, auto) 1fr; } +.sectionHeading { + color: var(--brand-color-grey-7); + margin-bottom: var(--space-s); +} + .fullWidth { grid-column: 1 / -1; } + +.description { + display: block; + + .sandboxLink { + color: var(--color-text-secondary); + display: flex; + align-items: center; + gap: var(--space-3xs); + width: fit-content; + + &:hover { + color: var(--brand-color-blue-7); + } + } +} + +.experimentalTag { + margin-bottom: var(--space-s); +} + +@media (--tablet-up) { + .description { + display: flex; + justify-content: space-between; + } +} diff --git a/src/app/(api)/api/[...slug]/page.tsx b/src/app/(api)/api/[...slug]/page.tsx index d641888e..f1867fb5 100644 --- a/src/app/(api)/api/[...slug]/page.tsx +++ b/src/app/(api)/api/[...slug]/page.tsx @@ -1,11 +1,15 @@ -import { ApiRequest, ApiResponses, TimeAgo, Heading, Paragraph } from '@/components'; +import { ApiRequest, ApiResponses, TimeAgo, Heading, Paragraph, Tag, Note } from '@/components'; import { loadMdxInfo } from '@/lib/markdown/util'; -import { parseSchema, toOperations } from '@/lib/swagger/parse'; +import { parseSchemas, toOperations } from '@/lib/swagger/parse'; import { toRouteSegments, toSlug } from '@/lib/util'; import { notFound } from 'next/navigation'; import type { Metadata } from 'next'; import { withMdxMetadata, withDefaultMetadata, getLastUpdated } from '@/lib/metadata/util'; +import { getMenuItem, getActiveAncestors } from '@/lib/menu/util'; import WithQuicknav from '@/components/WithQuickNav'; +import { Icon } from '@/icons'; +import { Link } from '@/components'; +import { cx } from 'class-variance-authority'; import styles from './page.module.css'; @@ -27,8 +31,8 @@ export async function generateMetadata({ params }: PageProps): Promise } // For Swagger operations, use the operation details - const schema = await parseSchema(); - const operations = toOperations(schema); + const schemas = await parseSchemas(); + const operations = toOperations(schemas); const operation = operations.find((op) => op.slug === qualifiedSlug); if (operation) { @@ -55,8 +59,8 @@ export const generateStaticParams = async () => { .map((info) => ({ slug: info.segments })); // Generate swagger slugs - const schema = await parseSchema(); - const operations = toOperations(schema); + const schemas = await parseSchemas(); + const operations = toOperations(schemas); const operationSlugs = operations.map((op) => ({ slug: toRouteSegments(op.slug) })); return mdxSlugs.concat(operationSlugs); @@ -70,13 +74,22 @@ const Page = async ({ params }: PageProps) => { const content = await loadMdxInfo('api'); const mdxInfo = content.find((info) => info.slug === qualifiedSlug); + const pathname = `${qualifiedSlug}`; + const menuData = getMenuItem('api'); + const ancestors = getActiveAncestors(pathname, [menuData]); + const parentTitle = ancestors.length > 1 ? ancestors[ancestors.length - 2].title : null; + if (mdxInfo) { - const mdxModule = await import(`@/content/${mdxInfo.file}`); - const { default: Post } = mdxModule; - const lastUpdated = getLastUpdated(mdxModule); + const { default: Post } = await import(`@/content/${mdxInfo.file}`); + const lastUpdated = await getLastUpdated(mdxInfo); return ( + {parentTitle ? ( +

+ {parentTitle} +

+ ) : null} {lastUpdated ? : null}
@@ -84,15 +97,42 @@ const Page = async ({ params }: PageProps) => { } // Otherwise render as an operation - const schema = await parseSchema(); - const operations = toOperations(schema); + const schemas = await parseSchemas(); + const operations = toOperations(schemas); const operation = operations.find((op) => op.slug === qualifiedSlug); if (operation) { + const operationParentTitle = + parentTitle || + (operation.menuSegments.length > 1 ? operation.menuSegments[operation.menuSegments.length - 2] : null); + return (
+ {operationParentTitle ? ( +

+ {operationParentTitle} +

+ ) : null} + {operation.experimental ? ( + + Early access + + ) : null} {operation.title} - {operation.description ? {operation.description} : null} +
+ {operation.description && {operation.description}} + {operation.sandboxLink && ( + + Open API Sandbox + + + )} +
+ {operation.experimental ? ( + + This endpoint is in early access, and may not be available to you. Contact us to request access + + ) : null}
diff --git a/src/app/(api)/api/page.tsx b/src/app/(api)/api/page.tsx index 6b73b3fb..20af68a0 100644 --- a/src/app/(api)/api/page.tsx +++ b/src/app/(api)/api/page.tsx @@ -35,9 +35,8 @@ const Page = async () => { const mdxInfo = content.find((info) => info.slug === ''); if (mdxInfo) { - const mdxModule = await import(`@/content/${mdxInfo.file}`); - const { default: Post } = mdxModule; - const lastUpdated = getLastUpdated(mdxModule); + const { default: Post } = await import(`@/content/${mdxInfo.file}`); + const lastUpdated = await getLastUpdated(mdxInfo); return ( diff --git a/src/app/(documentation)/[...slug]/page.module.css b/src/app/(documentation)/[...slug]/page.module.css new file mode 100644 index 00000000..b70b8ce3 --- /dev/null +++ b/src/app/(documentation)/[...slug]/page.module.css @@ -0,0 +1,4 @@ +.sectionHeading { + color: var(--brand-color-grey-7); + margin-bottom: var(--space-s); +} diff --git a/src/app/(documentation)/[...slug]/page.tsx b/src/app/(documentation)/[...slug]/page.tsx index 832c25d6..348328f6 100644 --- a/src/app/(documentation)/[...slug]/page.tsx +++ b/src/app/(documentation)/[...slug]/page.tsx @@ -4,7 +4,11 @@ import { notFound } from 'next/navigation'; import type { Metadata } from 'next'; import { withMdxMetadata, withDefaultMetadata, getLastUpdated } from '@/lib/metadata/util'; import { TimeAgo } from '@/components'; +import { getMenuItem, getActiveAncestors } from '@/lib/menu/util'; import WithQuicknav from '@/components/WithQuickNav'; +import { cx } from 'class-variance-authority'; + +import styles from './page.module.css'; export const dynamicParams = false; @@ -40,12 +44,26 @@ const Page = async ({ params }: PageProps) => { const mdxInfo = content.find((info) => info.slug === qualifiedSlug); if (mdxInfo) { - const mdxModule = await import(`@/content/${mdxInfo.file}`); - const { default: Post } = mdxModule; - const lastUpdated = getLastUpdated(mdxModule); + const { parentTitle: mdxParentTitle, default: Post } = await import(`@/content/${mdxInfo.file}`); + const repoPath = `src/content/${mdxInfo.file}`; + const lastUpdated = await getLastUpdated(mdxInfo); + + // 2. Original logic: Get parentTitle from the menu system. + const pathname = `/${qualifiedSlug}`; + const menuData = getMenuItem('documentation'); + const ancestors = getActiveAncestors(pathname, [menuData]); + const menuParentTitle = ancestors.length > 1 ? ancestors[ancestors.length - 2].title : null; + + // 3. Prioritize the title from the MDX file, then fall back to the menu. + const parentTitle = mdxParentTitle ?? menuParentTitle; return ( - + + {parentTitle ? ( +

+ {parentTitle} +

+ ) : null} {lastUpdated ? : null}
diff --git a/src/app/(documentation)/design-system/page.module.css b/src/app/(documentation)/design-system/page.module.css index 9c9c9734..4ead224a 100644 --- a/src/app/(documentation)/design-system/page.module.css +++ b/src/app/(documentation)/design-system/page.module.css @@ -1,6 +1,9 @@ .iconGrid { + --card-min-height: 116px; + --card-min-width: 166px; + display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr)); gap: var(--space-m); margin: var(--space-m) 0; } @@ -8,14 +11,56 @@ .iconWrapper { display: flex; flex-direction: column; - align-items: center; - padding: var(--space-m); + justify-content: space-between; + min-height: var(--card-min-height, 10em); + padding: var(--space-s) var(--space-m); border: 1px solid var(--color-border); - border-radius: var(--border-radius-m); + border-radius: var(--border-radius-s); + background: var(--brand-color-white); + text-decoration: none; + color: var(--color-text-primary); +} + +.iconWrapper:hover { + border-color: var(--color-accent-default); + background: var(--color-accent-default); +} + +.iconWrapper:hover header span, +.iconWrapper:hover .logo, +.iconWrapper:hover .arrowIcon { + color: var(--brand-color-white); +} + +.iconWrapper footer { + display: flex; + align-items: end; + justify-content: space-between; + margin-top: var(--space-m); +} + +.iconWrapper header span { + margin: 0; + color: var(--color-text-primary); +} + +.logo { + --logo-max-width: 30px; + --logo-max-height: 14px; + + color: var(--brand-color-grey-7); +} + +.arrowIcon { + margin-right: 0; + margin-left: auto; + color: var(--brand-color-white); + font-size: 1.3em; } -.iconName { - margin-top: var(--space-xs); - font-size: var(--text-body-xs); - text-align: center; +@media (--medium) { + .logo { + --logo-max-width: 40px; + --logo-max-height: 20px; + } } diff --git a/src/app/(guides)/guides/[...slug]/page.module.css b/src/app/(guides)/guides/[...slug]/page.module.css new file mode 100644 index 00000000..b70b8ce3 --- /dev/null +++ b/src/app/(guides)/guides/[...slug]/page.module.css @@ -0,0 +1,4 @@ +.sectionHeading { + color: var(--brand-color-grey-7); + margin-bottom: var(--space-s); +} diff --git a/src/app/(guides)/guides/[...slug]/page.tsx b/src/app/(guides)/guides/[...slug]/page.tsx index d13a1977..d934943f 100644 --- a/src/app/(guides)/guides/[...slug]/page.tsx +++ b/src/app/(guides)/guides/[...slug]/page.tsx @@ -3,8 +3,12 @@ import { toSlug } from '@/lib/util'; import { notFound } from 'next/navigation'; import type { Metadata } from 'next'; import { withMdxMetadata, withDefaultMetadata, getLastUpdated } from '@/lib/metadata/util'; +import { getMenuItem, getActiveAncestors } from '@/lib/menu/util'; import { TimeAgo } from '@/components'; import WithQuicknav from '@/components/WithQuickNav'; +import { cx } from 'class-variance-authority'; + +import styles from './page.module.css'; export const dynamicParams = false; @@ -40,12 +44,21 @@ const Page = async ({ params }: PageProps) => { const mdxInfo = content.find((info) => info.slug === qualifiedSlug); if (mdxInfo) { - const mdxModule = await import(`@/content/${mdxInfo.file}`); - const { default: Post } = mdxModule; - const lastUpdated = getLastUpdated(mdxModule); + const { default: Post } = await import(`@/content/${mdxInfo.file}`); + const lastUpdated = await getLastUpdated(mdxInfo); + + const pathname = `/${qualifiedSlug}`; + const menuData = getMenuItem('guides'); + const ancestors = getActiveAncestors(pathname, [menuData]); + const parentTitle = ancestors.length > 1 ? ancestors[ancestors.length - 2].title : null; return ( + {parentTitle ? ( +

+ {parentTitle} +

+ ) : null} {lastUpdated ? : null}
diff --git a/src/app/(guides)/guides/page.tsx b/src/app/(guides)/guides/page.tsx index a9e867e3..a05a69fe 100644 --- a/src/app/(guides)/guides/page.tsx +++ b/src/app/(guides)/guides/page.tsx @@ -33,9 +33,8 @@ const Page = async () => { const mdxInfo = content.find((info) => info.slug === ''); if (mdxInfo) { - const mdxModule = await import(`@/content/${mdxInfo.file}`); - const { default: Post } = mdxModule; - const lastUpdated = getLastUpdated(mdxModule); + const { default: Post } = await import(`@/content/${mdxInfo.file}`); + const lastUpdated = await getLastUpdated(mdxInfo); return ( diff --git a/src/app/_styles/critical.css b/src/app/_styles/critical.css index a83b165e..a8f38624 100644 --- a/src/app/_styles/critical.css +++ b/src/app/_styles/critical.css @@ -1,4 +1,5 @@ @import url('./variables.css'); @import url('./breakpoints.css'); +@import url('./typography.css'); @import url('./reset.css'); @import url('./global.css'); diff --git a/src/app/_styles/typography.css b/src/app/_styles/typography.css new file mode 100644 index 00000000..41677ebd --- /dev/null +++ b/src/app/_styles/typography.css @@ -0,0 +1,210 @@ +@layer typography { + /***************************************************** + * HEADLINE + *****************************************************/ + .headlineXL { + font-family: var(--font-family-headline); + font-size: var(--text-headline-2xl); + line-height: var(--line-height-none); + letter-spacing: var(--letter-spacing-negative-l); + } + + .headlineL { + font-family: var(--font-family-headline); + font-size: var(--text-headline-l); + line-height: var(--line-height-tight); + letter-spacing: var(--letter-spacing-negative-s); + } + + .headlineM { + font-family: var(--font-family-headline); + font-size: var(--text-headline-m); + line-height: var(--line-height-snug); + letter-spacing: var(--letter-spacing-negative-xs); + } + + .headlineS { + font-family: var(--font-family-headline); + font-size: var(--text-headline-s); + line-height: var(--line-height-snug); + letter-spacing: 0; + } + + .headlineXS { + font-family: var(--font-family-headline); + font-size: var(--text-body-l); + line-height: var(--line-height-snug); + letter-spacing: var(--letter-spacing-positive-xs); + } + + .headlineXXS { + font-family: var(--font-family-headline); + font-size: var(--text-headline-2xs); + line-height: var(--line-height-snug); + letter-spacing: var(--letter-spacing-positive-s); + } + + .headlineXXXS { + font-family: var(--font-family-headline); + font-size: var(--text-body-s); + line-height: var(--line-height-snug); + letter-spacing: var(--letter-spacing-positive-s); + } + + /***************************************************** + * BODY + *****************************************************/ + + /* NORMAL */ + .bodyXL { + font-family: var(--font-family-body); + font-size: var(--text-body-xl); + line-height: var(--line-height-normal); + letter-spacing: 0; + } + + .bodyL { + font-family: var(--font-family-body); + font-size: var(--text-body-l); + line-height: var(--line-height-normal); + letter-spacing: 0; + } + + .bodyM { + font-family: var(--font-family-body); + font-size: var(--text-body-m); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-xs); + } + + .bodyS { + font-family: var(--font-family-body); + font-size: var(--text-body-s); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-s); + } + + .bodyXS { + font-family: var(--font-family-body); + font-size: var(--text-body-xs); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-2xl); + } + + /* LONG */ + .bodyXLLong { + font-family: var(--font-family-body); + font-size: var(--text-body-xl); + line-height: var(--line-height-relaxed); + letter-spacing: 0; + } + + .bodyLLong { + font-family: var(--font-family-body); + font-size: var(--text-body-l); + line-height: var(--line-height-loose); + letter-spacing: 0; + } + + .bodyMLong { + font-family: var(--font-family-body); + font-size: var(--text-body-m); + line-height: var(--line-height-loose); + letter-spacing: var(--letter-spacing-positive-xs); + } + + .bodySLong { + font-family: var(--font-family-body); + font-size: var(--text-body-s); + line-height: var(--line-height-relaxed); + letter-spacing: var(--letter-spacing-positive-s); + } + + .bodyXSLong { + font-family: var(--font-family-body); + font-size: var(--text-body-xs); + line-height: var(--line-height-relaxed); + letter-spacing: var(--letter-spacing-positive-2xl); + } + + /***************************************************** + * MONO + *****************************************************/ + + /* CASE SENSITIVE */ + .monoXL { + font-family: var(--font-family-mono); + font-size: var(--text-body-l); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-l); + } + + .monoL { + font-family: var(--font-family-mono); + font-size: var(--text-body-m); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-3xl); + } + + .monoM { + font-family: var(--font-family-mono); + font-size: var(--text-body-s); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-3xl); + } + + .monoS { + font-family: var(--font-family-mono); + font-size: var(--text-body-xs); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-3xl); + } + + .monoXS { + font-family: var(--font-family-mono); + font-size: var(--text-body-2xs); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-4xl); + } + + /* UPPERCASE */ + .monoXLUppercase { + font-family: var(--font-family-mono); + font-size: var(--text-body-l); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-3xl); + text-transform: uppercase; + } + + .monoLUppercase { + font-family: var(--font-family-mono); + font-size: var(--text-body-m); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-5xl); + text-transform: uppercase; + } + + .monoMUppercase { + font-family: var(--font-family-mono); + font-size: var(--text-body-s); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-5xl); + text-transform: uppercase; + } + + .monoSUppercase { + font-family: var(--font-family-mono); + font-size: var(--text-body-xs); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-5xl); + text-transform: uppercase; + } + + .monoXSUppercase { + font-family: var(--font-family-mono); + font-size: var(--text-body-2xs); + line-height: var(--line-height-normal); + letter-spacing: var(--letter-spacing-positive-6xl); + text-transform: uppercase; + } +} diff --git a/src/app/_styles/variables.css b/src/app/_styles/variables.css index ed4a68f1..95b8dcb6 100644 --- a/src/app/_styles/variables.css +++ b/src/app/_styles/variables.css @@ -50,21 +50,103 @@ --base-color-yellow-700: #ff8f00; --base-color-yellow-800: #c87107; + /* Base colors (new) */ + --brand-color-white: #fff; + --brand-color-black: #0a1b2a; + --brand-color-blue-0: #fafcff; + --brand-color-blue-1: #ebf2fc; + --brand-color-blue-2: #dfebfe; + --brand-color-blue-3: #b7d2ff; + --brand-color-blue-4: #90b7f6; + --brand-color-blue-5: #6ca3fe; + --brand-color-blue-6: #498af6; + --brand-color-blue-7: #2a6fe1; + --brand-color-blue-8: #134596; + --brand-color-blue-9: #112b5e; + --brand-color-blue-10: #0c1325; + --brand-color-green-0: #f4fef9; + --brand-color-green-1: #e2fcf0; + --brand-color-green-2: #c4f4e0; + --brand-color-green-3: #85dabb; + --brand-color-green-4: #57c7a4; + --brand-color-green-5: #1bb694; + --brand-color-green-6: #00a084; + --brand-color-green-7: #007c68; + --brand-color-green-8: #005346; + --brand-color-green-9: #03322b; + --brand-color-green-10: #02120f; + --brand-color-grey-0: #f9fcff; + --brand-color-grey-1: #eff3fa; + --brand-color-grey-2: #e1eaf3; + --brand-color-grey-3: #c5d4de; + --brand-color-grey-4: #a5b7c5; + --brand-color-grey-5: #8ea5b5; + --brand-color-grey-6: #768fa2; + --brand-color-grey-7: #5b798a; + --brand-color-grey-8: #26465b; + --brand-color-grey-9: #092f45; + --brand-color-grey-10: #041e2e; + --brand-color-purple-0: #fbf9ff; + --brand-color-purple-1: #f6f2ff; + --brand-color-purple-2: #f0e5ff; + --brand-color-purple-3: #dcc7fc; + --brand-color-purple-4: #bd9eff; + --brand-color-purple-5: #af8fff; + --brand-color-purple-6: #915eff; + --brand-color-purple-7: #7a52db; + --brand-color-purple-8: #5527a4; + --brand-color-purple-9: #381a6b; + --brand-color-purple-10: #0f0522; + --brand-color-red-0: #fff8fa; + --brand-color-red-1: #fff2f5; + --brand-color-red-2: #ffe3e8; + --brand-color-red-3: #fcc0cb; + --brand-color-red-4: #fd93a8; + --brand-color-red-5: #ff6183; + --brand-color-red-6: #eb5070; + --brand-color-red-7: #b73b55; + --brand-color-red-8: #7f243a; + --brand-color-red-9: #541025; + --brand-color-red-10: #1d050b; + --brand-color-salmon-0: #fffbf9; + --brand-color-salmon-1: #fff4ef; + --brand-color-salmon-2: #ffe4d8; + --brand-color-salmon-3: #fec3ab; + --brand-color-salmon-4: #eca283; + --brand-color-salmon-5: #ed8565; + --brand-color-salmon-6: #db6c53; + --brand-color-salmon-7: #b7453b; + --brand-color-salmon-8: #7f251f; + --brand-color-salmon-9: #4a1d1a; + --brand-color-salmon-10: #1d0505; + --brand-color-yellow-0: #fefbf6; + --brand-color-yellow-1: #fdf6e8; + --brand-color-yellow-2: #ffe5b4; + --brand-color-yellow-3: #ffd482; + --brand-color-yellow-4: #e0aa40; + --brand-color-yellow-5: #d8982a; + --brand-color-yellow-6: #c08000; + --brand-color-yellow-7: #9c640e; + --brand-color-yellow-8: #6d4500; + --brand-color-yellow-9: #3f2604; + --brand-color-yellow-10: #1b0b00; + /* Backgrounds */ --color-background-default: var(--base-color-white); --color-background-light: var(--base-color-grey-25); --color-background-info: var(--base-color-blue-100); - --color-background-alert: var(--base-color-red-100); - --color-background-premium: var(--base-color-purple-100); - --color-background-success: var(--base-color-teal-100); - --color-background-warning: var(--base-color-yellow-100); + --color-background-alert: var(--brand-color-red-2); + --color-background-premium: var(--brand-color-purple-2); + --color-background-success: var(--brand-color-green-2); + --color-background-warning: var(--brand-color-yellow-2); --color-background-code: var(--base-color-blue-75); --color-background-pre: var(--base-color-grey-900); - --color-background-header: var(--base-color-grey-25); + --color-background-header: var(--brand-color-grey-0); /* Text */ - --color-text-primary: var(--base-color-grey-900); - --color-text-secondary: var(--base-color-grey-600); + --color-text-primary: var(--brand-color-grey-10); + --color-text-secondary: var(--brand-color-grey-8); + --color-text-tertiary: var(--brand-color-grey-7); --color-text-link: var(--base-color-blue-500); --color-text-on-color: var(--base-color-white); --color-text-label: var(--base-color-grey-500); @@ -76,7 +158,7 @@ --color-text-code: var(--base-color-blue-600); /* Border */ - --color-border: var(--base-color-grey-300); + --color-border: var(--brand-color-grey-3); --color-border-tabs: var(--base-color-blue-500); /* Accent */ @@ -97,6 +179,28 @@ --letter-http-type: 0.06px; --letter-http-rule: 0.06px; + --letter-spacing-negative-6xl: -0.06em; + --letter-spacing-negative-5xl: -0.04em; + --letter-spacing-negative-4xl: -0.03em; + --letter-spacing-negative-3xl: -0.02em; + --letter-spacing-negative-2xl: -0.015em; + --letter-spacing-negative-xl: -0.0125em; + --letter-spacing-negative-l: -0.01em; + --letter-spacing-negative-m: -0.0075em; + --letter-spacing-negative-s: -0.005em; + --letter-spacing-negative-xs: -0.0025em; + + --letter-spacing-positive-6xl: 0.06em; + --letter-spacing-positive-5xl: 0.04em; + --letter-spacing-positive-4xl: 0.03em; + --letter-spacing-positive-3xl: 0.02em; + --letter-spacing-positive-2xl: 0.015em; + --letter-spacing-positive-xl: 0.0125em; + --letter-spacing-positive-l: 0.01em; + --letter-spacing-positive-m: 0.0075em; + --letter-spacing-positive-s: 0.005em; + --letter-spacing-positive-xs: 0.0025em; + /* Text Sizes */ --text-body-2xs: fluid(8.5px, 10.5px); --text-body-xs: fluid(10px, 12px); @@ -130,6 +234,8 @@ --line-height-tight: 1.2; --line-height-snug: 1.222; --line-height-normal: 1.444; + --line-height-relaxed: 1.65; + --line-height-loose: 1.75; /* General Spacing */ --space-5xs: fluid(1px, 2px); @@ -169,8 +275,7 @@ --container-width-xs: 480px; --container-width-s: 740px; --container-width-m: 960px; - --container-width-l: 1400px; - --container-width-xl: 1512px; + --container-width-l: 1440px; --sidebar-width: 240px; /* Button fixed widths */ @@ -186,7 +291,7 @@ --layer-overlay: 200; /* Specific measurements */ - --navbar-top-height: 50px; + --navbar-top-height: 67px; --navbar-bottom-height: 50px; --navbar-height: calc(var(--navbar-top-height) + var(--navbar-bottom-height)); @@ -198,5 +303,9 @@ @media (--laptop-up) { --container-margin-home: var(--space-2xl); } + + @media (--desktop-up) { + --sidebar-width: 272px; + } } } diff --git a/src/app/page.module.css b/src/app/page.module.css index 5b8d8fa5..ea2cb294 100644 --- a/src/app/page.module.css +++ b/src/app/page.module.css @@ -1,7 +1,7 @@ .section { - max-width: var(--container-width-xl); + max-width: var(--container-width-l); margin: 0 auto; - padding: var(--space-l) var(--container-margin-home); + padding: var(--space-l) var(--space-m); } .section:first-of-type { @@ -10,11 +10,10 @@ .sectionHeading { margin-bottom: var(--space-m); - font-size: var(--text-body-m); } .divider { - max-width: var(--container-width-xl); + max-width: var(--container-width-l); margin: 0 auto; padding: var(--space-m); } @@ -24,9 +23,3 @@ height: 1px; background-color: var(--color-border); } - -@media (--phablet-up) { - .sectionHeading { - font-size: var(--text-body-xl); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx index e550be89..82957737 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,6 @@ import { Card, Flex, HomepageHero, Container } from '@/components'; import { HomepageContent, Section, Card as CardType } from '@/lib/types'; +import { cx } from 'class-variance-authority'; import styles from './page.module.css'; import content from '@/content/homepage.json'; @@ -11,7 +12,7 @@ export default function Page() { case 'cards': return ( -

{section.heading}

+

{section.heading}

{section.cards.map((card: CardType, cardIndex: number) => ( ))} diff --git a/src/components/ApiGrid/ApiGrid.module.css b/src/components/ApiGrid/ApiGrid.module.css index a2b4ddeb..d6e90404 100644 --- a/src/components/ApiGrid/ApiGrid.module.css +++ b/src/components/ApiGrid/ApiGrid.module.css @@ -34,14 +34,10 @@ border-top: 0; border-radius: var(--border-radius-m) var(--border-radius-m) 0 0; background-color: var(--color-background-light); - text-transform: uppercase; - font-family: var(--font-family-mono); - font-size: var(--text-body-xs); - color: var(--base-color-grey-600); + color: var(--brand-color-grey-7); } .subItem { - line-height: var(--line-height-s); padding-inline: var(--column-inline-padding); padding-block: var(--column-block-padding); @@ -55,7 +51,6 @@ .subItemType { color: var(--base-color-blue-500); font-family: var(--font-family-mono); - font-size: var(--text-body-xs); letter-spacing: var(--letter-http-type); } diff --git a/src/components/ApiGrid/ApiGrid.tsx b/src/components/ApiGrid/ApiGrid.tsx index bf8ea1e2..b4b8107f 100644 --- a/src/components/ApiGrid/ApiGrid.tsx +++ b/src/components/ApiGrid/ApiGrid.tsx @@ -8,7 +8,7 @@ import styles from './ApiGrid.module.css'; export const ApiGrid = ({ heading, children }: Grid) => (
-
{heading}
+
{heading}
{children} diff --git a/src/components/ApiMedia/ApiMedia.module.css b/src/components/ApiMedia/ApiMedia.module.css index ab5cb8c1..1b476dd9 100644 --- a/src/components/ApiMedia/ApiMedia.module.css +++ b/src/components/ApiMedia/ApiMedia.module.css @@ -5,9 +5,6 @@ .responseTitle { color: var(--base-color-grey-600); - font-family: var(--font-family-mono); - font-size: var(--text-body-xs); - text-transform: uppercase; } .responseTypeContent { @@ -18,7 +15,6 @@ .responseTypeTitle { color: var(--base-color-grey-600); - font-size: var(--text-body-xs); } .responseGrid { diff --git a/src/components/ApiMedia/ApiMedia.tsx b/src/components/ApiMedia/ApiMedia.tsx index f27de6e9..e3edda94 100644 --- a/src/components/ApiMedia/ApiMedia.tsx +++ b/src/components/ApiMedia/ApiMedia.tsx @@ -13,7 +13,9 @@ export const ApiMediaResponse = (response: ResponseObject | RequestBodyObject) = <> {response.content ? (
-

{response.description || 'Response body'}

+

+ {response.description || 'Response body'} +

{Object.entries(response.content).map(([media, content]) => ( @@ -29,7 +31,9 @@ const Schema = ({ schema, description }: { media?: string; schema: SchemaObject; if (schema.type === 'array') { return ( <> -

{description || `${schema.type} of ${schema.items.type}s`}

+

+ {description || `${schema.type} of ${schema.items.type}s`} +

@@ -40,7 +44,7 @@ const Schema = ({ schema, description }: { media?: string; schema: SchemaObject; if (schema.type === 'object') { return ( <> -

{description || schema.type}

+

{description || schema.type}

@@ -54,7 +58,7 @@ const Schema = ({ schema, description }: { media?: string; schema: SchemaObject; const Properties = ({ properties, required, type }: SchemaObject) => { return ( <> - {type ?

{type}

: null} + {type ?

{type}

: null} {properties ? (
diff --git a/src/components/ApiRequest/ApiRequest.tsx b/src/components/ApiRequest/ApiRequest.tsx index 7a4f1c06..43c21bed 100644 --- a/src/components/ApiRequest/ApiRequest.tsx +++ b/src/components/ApiRequest/ApiRequest.tsx @@ -15,7 +15,7 @@ export const ApiRequest = (operation: ApiOperation) => {
- {`${process.env.NEXT_PUBLIC_CLOUDSMITH_API_URL}${operation.path}`} + {`${process.env.NEXT_PUBLIC_CLOUDSMITH_API_URL}/${operation.version}${operation.path}`}
diff --git a/src/components/AppShell/AppShell.module.css b/src/components/AppShell/AppShell.module.css index 74709600..2059ca06 100644 --- a/src/components/AppShell/AppShell.module.css +++ b/src/components/AppShell/AppShell.module.css @@ -8,6 +8,13 @@ top: var(--navbar-top-height); z-index: var(--layer-navigation); background-color: var(--base-color-grey-1000); + + &::-webkit-scrollbar { + display: none; + } + + scrollbar-width: none; + -ms-overflow-style: none; } @media (--phablet-up) { @@ -24,19 +31,24 @@ .secondaryNav { z-index: 0; flex: 0 0 var(--sidebar-width); - border-right: 1px solid var(--color-border); - border-left: 1px solid var(--color-border); - background-color: var(--color-background-light); + background-color: transparent; height: calc(100vh - var(--navbar-height)); overflow-y: auto; } .main { flex: 1; - padding-left: var(--space-xl); - padding-right: var(--space-xl); + padding-left: var(--space-3xl); + padding-right: var(--space-3xl); /* Make sure that pre tags don't expand the tags */ min-width: 0; } } + +@media (--desktop-up) { + .main { + padding-left: var(--space-3xl); + padding-right: var(--space-3xl); + } +} diff --git a/src/components/Button/Button.module.css b/src/components/Button/Button.module.css index 8add8cae..a1ef5ade 100644 --- a/src/components/Button/Button.module.css +++ b/src/components/Button/Button.module.css @@ -2,7 +2,7 @@ --notch-size: 5px; display: inline-flex; - padding: 1em 1.25em 0.85em; + padding: var(--space-xs) var(--space-s); border: 0; font-family: var(--font-family-headline); font-size: var(--text-body-m); @@ -154,6 +154,12 @@ } } +.icon { + width: 16px; + height: 16px; + opacity: 0.75; +} + .root:hover .secondaryBorderFill { display: none; } @@ -185,3 +191,10 @@ opacity: 0.5; pointer-events: none; } + +@media (--phablet-up) { + .root.widthFull { + width: fit-content; + justify-content: space-between; + } +} diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 02e3e49a..5f097747 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,4 +1,4 @@ -import { Icon } from '@/icons'; +import { Icon, type IconName } from '@/icons'; import { ButtonColorScheme, ButtonSize, ButtonVariant, ButtonWidth } from '@/lib/types'; import { cva, cx, type VariantProps } from 'class-variance-authority'; import Link from 'next/link'; @@ -47,6 +47,7 @@ interface ButtonProps withArrow?: boolean; isExternalLink?: boolean; className?: string; + icon?: IconName; } export function Button({ @@ -58,6 +59,7 @@ export function Button({ className, width, withArrow = false, + icon, disabled, isExternalLink = false, ...rest @@ -72,6 +74,7 @@ export function Button({ const content = ( <> + {icon && } {children} {withArrow && } {variant === 'secondary' && ( diff --git a/src/components/Card/Card.module.css b/src/components/Card/Card.module.css index 18d3bbdf..02c56601 100644 --- a/src/components/Card/Card.module.css +++ b/src/components/Card/Card.module.css @@ -3,10 +3,10 @@ text-decoration: none; border-radius: var(--border-radius-2xl); background-color: var(--color-background-default); - border: 1px solid var(--color-background-info); + border: 1px solid var(--color-border); display: flex; flex-direction: column; - gap: var(--space-s); + gap: var(--space-m); padding: var(--space-m); min-height: 200px; } @@ -33,7 +33,6 @@ } .description { - font-family: var(--font-family-body); padding-bottom: var(--space-2xs); } @@ -42,6 +41,7 @@ align-items: center; gap: var(--space-2xs); font-family: var(--font-family-headline); + margin-top: var(--space-2xs); } /* Size variants */ @@ -82,45 +82,86 @@ } .sizeSmall { - .title { - font-size: var(--text-body-m); + .link { + font-size: var(--text-body-xs); } +} - .description { +.sizeMedium { + .link { font-size: var(--text-body-s); } +} +.sizeLarge { .link { - font-size: var(--text-body-xs); + font-size: var(--text-body-m); + justify-content: space-between; } } -.sizeMedium { - .title { - font-size: var(--text-body-l); +/* Type variants */ + +.typeSimple { + padding: var(--space-s) var(--space-m); + gap: var(--space-2xs); + width: 100%; + min-height: unset; + height: fit-content; + position: relative; + + .top, + .bottom { + max-width: calc(100% - var(--space-xl)); } - .description { - font-size: var(--text-body-m); + .top { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--space-2xs); } - .link { - font-size: var(--text-body-s); + .icon { + flex-shrink: 0; } -} -.sizeLarge { - .title { - font-size: var(--text-headline-s); + .icon svg { + width: 24px; + height: 24px; } .description { - font-size: var(--text-body-m); + padding-bottom: 0; + color: var(--color-text-secondary); } - .link { - font-size: var(--text-body-m); - justify-content: space-between; + .arrow { + position: absolute; + right: var(--space-m); + top: 50%; + transform: translateY(-50%); + color: var(--brand-color-grey-7); + } + + &:hover { + background-color: var(--brand-color-blue-7); + color: var(--brand-color-white); + border: 1px solid transparent; + + .icon svg { + color: var(--brand-color-white); + } + + .description { + color: var(--brand-color-white); + opacity: 0.6; + } + + .arrow { + color: var(--brand-color-white); + opacity: 0.6; + } } } @@ -128,10 +169,6 @@ text-decoration: underline; } -.content:has(.description) .link { - margin-top: auto; -} - .content:not(:has(.description)) { justify-content: flex-end; } diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 93ef4f1b..9ad15bdd 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,4 +1,4 @@ -import { type VariantProps, cva } from 'class-variance-authority'; +import { type VariantProps, cva, cx } from 'class-variance-authority'; import Link from 'next/link'; import { Icon, IconName } from '@/icons'; import styles from './Card.module.css'; @@ -18,10 +18,10 @@ const card = cva(styles.root, { half: styles.widthHalf, full: styles.widthFull, }, - }, - defaultVariants: { - size: 'm', - width: 'third', + type: { + default: '', + simple: styles.typeSimple, + }, }, }); @@ -35,32 +35,58 @@ export function Card({ backgroundImage, size, width, + type, className, ...rest }: CardProps) { return ( - - {backgroundImage && ( -