From 5f78f23c731e07413df1ea69473aa8546933b6b5 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Wed, 1 Oct 2025 13:05:51 +0200 Subject: [PATCH 01/14] Merge branch 'heroku-migration-video-blog' of https://github.com/DefangLabs/defang-docs into heroku-migration-video-blog --- .gitignore | 2 + components.json | 20 + docusaurus.config.js | 15 + package-lock.json | 718 ++++++++++++++++++++++++++++++++++- package.json | 10 + playwright.config.ts | 34 ++ postcss.config.js | 6 + scripts/prebuild.sh | 5 + src/components/ui/badge.tsx | 29 ++ src/components/ui/button.tsx | 52 +++ src/components/ui/card.tsx | 64 ++++ src/css/custom.css | 411 +++++++++++++++----- src/lib/utils.ts | 6 + src/pages/index.tsx | 239 +++++++++++- tailwind.config.js | 88 +++++ tests/visual/docs-ui.spec.ts | 48 +++ tests/visual/home.spec.ts | 41 ++ tsconfig.json | 7 +- 18 files changed, 1689 insertions(+), 106 deletions(-) create mode 100644 components.json create mode 100644 playwright.config.ts create mode 100644 postcss.config.js create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/lib/utils.ts create mode 100644 tailwind.config.js create mode 100644 tests/visual/docs-ui.spec.ts create mode 100644 tests/visual/home.spec.ts diff --git a/.gitignore b/.gitignore index b0408bb9b2..65db4d1b64 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ defang samples /docs/samples.md /static/samples-v2.json +playwright-report +test-results diff --git a/components.json b/components.json new file mode 100644 index 0000000000..aa7df05afa --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/css/custom.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/docusaurus.config.js b/docusaurus.config.js index 28091c5528..39e20003c6 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,6 +1,7 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion const { themes } = require('prism-react-renderer'); +const path = require('path'); const lightCodeTheme = themes.github; const darkCodeTheme = themes.dracula; @@ -206,6 +207,20 @@ const config = { }, }, plugins: [ + async function shadcnTailwindPlugin() { + return { + name: 'defang-tailwind-shadcn', + configureWebpack() { + return { + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + }; + }, + }; + }, require.resolve('docusaurus-lunr-search'), [ '@docusaurus/plugin-client-redirects', diff --git a/package-lock.json b/package-lock.json index 38c9fe8b52..1d5fa00e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,22 +17,32 @@ "@emotion/styled": "^11.11.0", "@mdx-js/react": "^3.0.0", "@mui/material": "^5.14.14", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-slot": "^1.2.3", "change-case": "^5.4.4", + "class-variance-authority": "^0.7.1", "clsx": "^1.2.1", "docusaurus-lunr-search": "^3.2.0", "fuse.js": "^7.0.0", "gray-matter": "^4.0.3", + "lucide-react": "^0.544.0", "prism-react-renderer": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.1", + "tailwind-merge": "^3.3.1", "yaml": "^2.4.5" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.0.0", "@docusaurus/tsconfig": "3.0.0", "@docusaurus/types": "3.0.0", + "@playwright/test": "^1.55.1", "@types/react": "^18.2.29", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.13", + "tailwindcss-animate": "^1.0.7", "typescript": "~5.2.2" }, "engines": { @@ -200,6 +210,18 @@ "@algolia/requester-common": "4.22.1" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -2664,6 +2686,50 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -3109,6 +3175,31 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", + "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", + "dev": true, + "dependencies": { + "playwright": "1.55.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -3160,6 +3251,45 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4224,6 +4354,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4715,6 +4851,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -4902,6 +5047,25 @@ "node": ">=8" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -6402,6 +6566,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -6433,6 +6603,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -7436,6 +7612,34 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", @@ -9426,6 +9630,21 @@ "node": ">=0.10.0" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -9772,6 +9991,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -12522,6 +12749,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", @@ -12564,6 +12800,17 @@ "multicast-dns": "cli.js" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -12735,6 +12982,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -12928,6 +13184,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -13068,6 +13330,28 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -13110,6 +13394,24 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -13191,10 +13493,54 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", + "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "dev": true, + "dependencies": { + "playwright-core": "1.55.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", + "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -13210,7 +13556,7 @@ } ], "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -13320,6 +13666,95 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/postcss-loader": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", @@ -13528,6 +13963,31 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", @@ -14365,6 +14825,15 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -15558,6 +16027,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -15620,6 +16110,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -15675,6 +16178,81 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -15793,6 +16371,73 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -15888,6 +16533,27 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -16019,6 +16685,12 @@ "node": ">=6.10" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -17100,6 +17772,44 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", diff --git a/package.json b/package.json index d6bc40a35f..8ef956c705 100644 --- a/package.json +++ b/package.json @@ -26,22 +26,32 @@ "@emotion/styled": "^11.11.0", "@mdx-js/react": "^3.0.0", "@mui/material": "^5.14.14", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-slot": "^1.2.3", "change-case": "^5.4.4", + "class-variance-authority": "^0.7.1", "clsx": "^1.2.1", "docusaurus-lunr-search": "^3.2.0", "fuse.js": "^7.0.0", "gray-matter": "^4.0.3", + "lucide-react": "^0.544.0", "prism-react-renderer": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.1", + "tailwind-merge": "^3.3.1", "yaml": "^2.4.5" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.0.0", "@docusaurus/tsconfig": "3.0.0", "@docusaurus/types": "3.0.0", + "@playwright/test": "^1.55.1", "@types/react": "^18.2.29", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.13", + "tailwindcss-animate": "^1.0.7", "typescript": "~5.2.2" }, "browserslist": { diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..51493d550a --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test'; + +const PORT = process.env.PORT ? Number(process.env.PORT) : 3100; +const HOST = '127.0.0.1'; + +export default defineConfig({ + testDir: './tests', + timeout: 120_000, + expect: { + timeout: 10_000, + }, + fullyParallel: true, + retries: process.env.CI ? 2 : 0, + use: { + baseURL: `http://${HOST}:${PORT}`, + trace: 'retain-on-failure', + viewport: { width: 1440, height: 900 }, + colorScheme: 'dark', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: { + command: `DEFANG_SKIP_GENERATION=1 npm run start -- --host ${HOST} --port ${PORT} --no-open`, + url: `http://${HOST}:${PORT}`, + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + timeout: 120_000, + }, +}); diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index 6717004dc7..0da8c290d8 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -2,6 +2,11 @@ set -e +if [ "$DEFANG_SKIP_GENERATION" = "1" ]; then + echo 'Skipping CLI and sample generation (DEFANG_SKIP_GENERATION=1)'; + exit 0 +fi + CWD=$(pwd) CLI_DOCS_PATH=$(readlink -f docs/cli) diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000000..b9ad2376f1 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const badgeVariants = cva( + 'inline-flex items-center rounded-full border px-3 py-1 text-xs font-semibold uppercase tracking-wide transition-colors', + { + variants: { + variant: { + default: 'border-transparent bg-secondary text-secondary-foreground shadow-sm', + outline: 'text-muted-foreground border-border/80' + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +export function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000000..6b67fe995e --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background', + { + variants: { + variant: { + default: 'bg-gradient-to-r from-primary/95 to-primary text-primary-foreground shadow-glow hover:translate-y-0.5 hover:shadow-lg', + subtle: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-11 px-6 py-2', + sm: 'h-9 rounded-full px-4', + lg: 'h-12 rounded-full px-8 text-base', + icon: 'h-11 w-11' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000000..a1e337bd75 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = 'Card'; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = 'CardTitle'; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = 'CardDescription'; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = 'CardContent'; + +export { Card, CardHeader, CardTitle, CardDescription, CardContent }; diff --git a/src/css/custom.css b/src/css/custom.css index f116f85b42..7bd14a1e22 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,11 +1,9 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); +@tailwind base; +@tailwind components; +@tailwind utilities; + @font-face { font-family: 'GuarujaNeue-SemiBold'; src: url('/fonts/guaruja-neue/GuarujaNeue-SemiBold.woff2') format('woff2'), @@ -14,131 +12,352 @@ font-style: normal; } +:root { + color-scheme: light; + --font-sans: 'Inter', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --font-display: 'GuarujaNeue-SemiBold', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --background: 216 65% 99%; + --foreground: 222 76% 6%; + --muted: 215 40% 93%; + --muted-foreground: 219 19% 35%; + --popover: 0 0% 100%; + --popover-foreground: 222 76% 6%; + --card: 0 0% 100%; + --card-foreground: 222 76% 6%; + --border: 216 36% 90%; + --input: 216 36% 86%; + --primary: 212 92% 47%; + --primary-foreground: 210 40% 98%; + --secondary: 215 52% 92%; + --secondary-foreground: 222 76% 12%; + --accent: 221 52% 94%; + --accent-foreground: 222 76% 12%; + --destructive: 0 84% 60%; + --destructive-foreground: 0 0% 100%; + --ring: 212 92% 47%; + --radius: 1rem; -h1, -h2, -h3, -h4, -h5, -h6, -#__docusaurus_skipToContent_fallback>div>div>aside>nav>div, -#__docusaurus>nav>div.navbar__inner>div:nth-child(1)>a { - font-family: "GuarujaNeue-SemiBold", sans-serif; - font-optical-sizing: auto; - font-weight: 600; - font-style: normal; + --ifm-color-primary: hsl(212, 92%, 47%); + --ifm-color-primary-dark: hsl(212, 92%, 42%); + --ifm-color-primary-darker: hsl(212, 92%, 38%); + --ifm-color-primary-darkest: hsl(212, 92%, 32%); + --ifm-color-primary-light: hsl(212, 92%, 60%); + --ifm-color-primary-lighter: hsl(212, 92%, 66%); + --ifm-color-primary-lightest: hsl(212, 92%, 72%); + --ifm-font-family-base: var(--font-sans); + --ifm-font-family-monospace: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + --ifm-heading-font-family: var(--font-display); + --ifm-heading-font-weight: 600; + --ifm-navbar-shadow: none; + --ifm-navbar-padding-vertical: 0.75rem; + --ifm-navbar-padding-horizontal: 1.25rem; + --ifm-menu-link-padding-horizontal: 0.75rem; + --ifm-code-font-size: 0.9rem; + --ifm-global-radius: var(--radius); + --ifm-toc-border-color: hsla(var(--border) / 0.8); + --docusaurus-highlighted-code-line-bg: hsla(var(--primary) / 0.1); + --prism-background-color: hsl(213 55% 97%); + --prism-color: hsl(223 41% 18%); + --ifm-code-background: var(--prism-background-color); + --ifm-pre-background: var(--prism-background-color); + --ifm-footer-background-color: transparent; + --ifm-code-border-radius: 1.25rem; + --ifm-tabs-border-bottom: 1px solid hsla(var(--border) / 0.7); + --ifm-tabs-border-radius: 0.875rem; + --ifm-footer-title-color: hsl(var(--foreground)); } -/* You can override the default Infima variables here. */ -:root { - --ifm-color-primary: #056ebc; - --ifm-color-primary-dark: #0563a9; - --ifm-color-primary-darker: #045da0; - --ifm-color-primary-darkest: #044d84; - --ifm-color-primary-light: #0679cf; - --ifm-color-primary-lighter: #067ed8; - --ifm-color-primary-lightest: #078ff4; - --ifm-code-font-size: 95%; - --ifm-font-family-base: 'Inter'; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { - --ifm-color-primary: #7eb7e2; - --ifm-color-primary-dark: #61a7dc; - --ifm-color-primary-darker: #539fd8; - --ifm-color-primary-darkest: #2d86c9; - --ifm-color-primary-light: #9bc7e8; - --ifm-color-primary-lighter: #a9cfec; - --ifm-color-primary-lightest: #d4e7f5; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + color-scheme: dark; + --background: 222 62% 7%; + --foreground: 220 19% 92%; + --muted: 220 30% 20%; + --muted-foreground: 218 12% 72%; + --popover: 225 48% 10%; + --popover-foreground: 210 40% 98%; + --card: 222 45% 9%; + --card-foreground: 210 40% 98%; + --border: 220 28% 22%; + --input: 220 29% 26%; + --primary: 212 90% 60%; + --primary-foreground: 210 40% 8%; + --secondary: 219 36% 24%; + --secondary-foreground: 210 40% 96%; + --accent: 219 36% 24%; + --accent-foreground: 210 40% 96%; + --destructive: 0 62% 48%; + --destructive-foreground: 0 0% 100%; + --ring: 212 90% 60%; + --docusaurus-highlighted-code-line-bg: hsla(var(--primary) / 0.18); + --prism-background-color: hsl(222 42% 18%); + --prism-color: hsl(220 19% 92%); + --ifm-code-background: var(--prism-background-color); + --ifm-pre-background: var(--prism-background-color); + --ifm-footer-background-color: transparent; +} + +@layer base { + *, + *::before, + *::after { + border-color: hsl(var(--border)); + } + + html { + scroll-behavior: smooth; + height: 100%; + } + + #__docusaurus { + @apply bg-background text-foreground antialiased; + font-family: var(--font-sans); + background-image: radial-gradient(circle at top left, hsla(var(--primary) / 0.08), transparent 55%), radial-gradient(circle at 75% 10%, hsla(var(--secondary) / 0.12), transparent 45%); + background-attachment: fixed; + } + + [data-theme='dark'] #__docusaurus { + @apply bg-background text-foreground antialiased; + font-family: var(--font-sans); + background-image: radial-gradient(circle at 20% 15%, hsla(var(--primary) / 0.12), transparent 45%), + radial-gradient(circle at 80% 5%, hsla(var(--secondary) / 0.08), transparent 42%); + background-attachment: fixed; + } + + + h1, + h2, + h3, + h4, + h5, + h6, + .navbar__title { + font-family: var(--font-display); + letter-spacing: -0.01em; + } +} + +@layer components { + .navbar { + @apply bg-transparent backdrop-blur-md border-b border-border/80 transition-colors duration-300; + } + + .navbar__inner { + @apply max-w-screen-2xl mx-auto px-4; + } + + .navbar__logo { + @apply scale-95 hover:scale-100 transition-transform duration-300; + } + + .navbar__items--right { + @apply gap-4; + } + + .navbar__item-get_started { + @apply inline-flex items-center justify-center rounded-full text-sm font-medium text-primary-foreground shadow-glow px-5 py-2 transition-all mr-2; + background: linear-gradient(135deg, hsla(var(--primary) / 1) 0%, hsla(var(--primary) / 0.8) 100%); + } + + .navbar__item-get_started:hover { + @apply translate-y-0.5 shadow-lg; + background: linear-gradient(135deg, hsla(var(--primary) / 0.92) 0%, hsla(var(--primary) / 0.72) 100%); + box-shadow: 0 18px 35px -18px hsla(var(--primary) / 0.65); + color: hsl(var(--primary-foreground)); + } + + .menu__link--sublist { + @apply rounded-lg; + } + + .theme-code-block { + background: var(--prism-background-color) !important; + border: 1px solid hsla(var(--border) / 0.75); + box-shadow: 0 22px 40px -30px hsla(var(--primary) / 0.4); + border-radius: 1.25rem; + overflow: hidden; + } + + .theme-code-block pre { + background: transparent !important; + color: var(--prism-color); + } + + .theme-code-block code { + background: transparent; + } + + .theme-code-block__header { + background: hsla(var(--background) / 0.9); + border-bottom: 1px solid hsla(var(--border) / 0.6); + } + + .docusaurus-highlight-code-line { + background: hsla(var(--primary) / 0.12); + } + + .toggleNode { + @apply text-muted-foreground hover:text-foreground; + } + + .pagination-nav__link { + @apply rounded-full border border-border/80 bg-card text-sm font-medium hover:border-primary/60 hover:text-primary-foreground hover:bg-primary/90; + } + + .breadcrumbs__link, + .breadcrumbs__item--active { + @apply text-sm font-medium text-muted-foreground hover:text-foreground; + } + + .table-of-contents { + @apply bg-card/60 backdrop-blur-sm border border-border/70 rounded-2xl px-4 py-4 text-sm shadow-sm; + } + + .footer__title { + @apply text-sm uppercase tracking-wider text-foreground/80; + } + + .footer__link-item { + @apply text-muted-foreground hover:text-primary transition-colors; + } + + .docCard { + @apply border border-border/70 rounded-2xl overflow-hidden shadow-sm hover:shadow-lg hover:shadow-primary/20 transition-all duration-300; + } + + .docCard:hover .docCard__title { + @apply text-primary; + } + + .theme-doc-sidebar-menu .menu__link { + @apply rounded-lg text-sm font-medium text-muted-foreground hover:text-primary; + } + + .theme-doc-sidebar-menu .menu__link--active { + @apply bg-secondary text-secondary-foreground shadow-sm; + } + + .theme-doc-markdown code { + @apply bg-muted/60 px-1.5 py-0.5 rounded-md text-[0.92em]; + } + + .theme-doc-footer { + @apply border-t border-border/60; + } } -.docs-doc-id-ask h1 { - display: none; + +.tabs-container > div { + /* rounded bottom corners */ + @apply rounded-b-lg p-4 border border-border/70 dark:border-border/60 bg-background dark:bg-card; + + margin-top: 0px !important; } -.docs-doc-id-ask .breadcrumbs { - display: none; +.tabs__list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + border-bottom: 1px solid hsla(var(--border) / 0.7); + padding-bottom: 0.5rem; } -.docs-doc-id-ask main>.container { - padding: 0px !important; +.tabs__item { + border-radius: 0.875rem 0.875rem 0 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border: 1px solid transparent; + border-bottom: 2px solid transparent; + padding: 0.5rem 1rem; + font-size: 0.9375rem; + font-weight: 600; + color: hsl(var(--muted-foreground)); + background: hsla(var(--background) / 0.78); + transition: all 0.25s ease; } -.docs-doc-id-ask article>nav { - display: none !important; +.tabs__item:not(.tabs__item--active):hover { + color: hsl(var(--foreground)); + border-color: hsla(var(--border) / 0.85); + background: hsla(var(--background) / 0.92); } -.docs-doc-id-ask main>.container>.row>.col { - max-width: 100% !important; +.tabs__item--active { + color: hsl(var(--foreground)); + background: hsl(var(--background)); + border-color: hsla(var(--border) / 0.9); + box-shadow: 0 18px 32px -24px hsla(var(--primary) / 0.55); + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-bottom: 2px solid hsla(var(--primary) / 0.9); } -.docs-doc-id-ask main nav.pagination-nav { - display: none !important; +[data-theme='dark'] .tabs__item { + background: hsla(var(--card) / 0.55); + color: hsl(var(--muted-foreground)); } -.docs-doc-id-ask main article footer { - display: none !important; +[data-theme='dark'] .tabs__item--active { + background: hsl(var(--card)); + border-color: hsla(var(--border) / 0.7); + color: hsl(var(--foreground)); + box-shadow: 0 18px 34px -26px hsla(var(--primary) / 0.6); + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-bottom: 2px solid hsla(var(--primary) / 0.9); } -.blog-post-page main img, -.blog-list-page article img { - border-radius: 50px; - box-shadow: 0px 10px 20px -5px rgba(0, 0, 0, 0.25); + + +footer.footer--dark { + position: relative; + background-image: linear-gradient(135deg, #011d50 0%, #093e9f 100%) !important; + background-color: #011d50 !important; + color: rgba(255, 255, 255, 0.92); + box-shadow: 0 -18px 48px -32px rgba(1, 29, 80, 0.6); } -.MuiButtonBase-root p { - margin: 0px; +footer.footer--dark .footer__title { + color: rgba(255, 255, 255, 0.82); } -a.MuiButtonBase-root:hover { - color: white !important; +footer.footer--dark .footer__link-item { + color: rgba(255, 255, 255, 0.72); } -/* Tentative change to address dark mode text on Samples page. -To go further, consider looking at implementing MUI custom themes. -*/ -.MuiTypography-root, -.MuiFormLabel-root, -.MuiInputBase-input { - color: var(--ifm-font-color-base) !important; +footer.footer--dark .footer__link-item:hover { + color: rgba(255, 255, 255, 0.95); } -img.unstyled { - border-radius: 0px !important; - box-shadow: none !important; +.docs-doc-id-ask { + --docusaurus-content-width: 100%; } -.navbar__item-get_started { - color: white; - border-radius: 1rem; - background-size: 200% 100%; - background-position: 0 0; - background-repeat: repeat-x; - background-image: linear-gradient(131deg, rgba(1, 30, 80, 0.9), rgba(21, 103, 251, 0.9) 50%, rgba(1, 30, 80, 0.9)); - margin-right: 1rem; - display: inline-block; - font-size: smaller; +.docs-doc-id-ask h1, +.docs-doc-id-ask .breadcrumbs, +.docs-doc-id-ask main article footer, +.docs-doc-id-ask main nav.pagination-nav { + display: none !important; } -.navbar__item-get_started:hover { - color: white; - background-position: 200% 0; - animation: shimmer 0.25s ease-in forwards; +.docs-doc-id-ask main > .container { + padding: 0 !important; } -@keyframes shimmer { - from { - background-position: 0 0; - } +.docs-doc-id-ask article > nav { + display: none !important; +} - to { - background-position: 200% 0; - } +.docs-doc-id-ask main > .container > .row > .col { + max-width: 100% !important; } -.menu__list-item.hidden { - display: none; +.blog-post-page main img, +.blog-list-page article img { + border-radius: 2rem; + box-shadow: 0px 18px 45px -24px rgba(9, 28, 61, 0.45); +} + +img.unstyled { + border-radius: 0 !important; + box-shadow: none !important; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000000..9ad0df4269 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5419eaec55..a231d05962 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,10 +1,239 @@ -import { useEffect } from 'react'; +import { ArrowRight, Boxes, GitBranch, Sparkles, Terminal } from 'lucide-react'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; + +const featureCards = [ + { + title: 'Opinionated building blocks', + description: + 'Compose production-ready stacks with recipes for apps, workers, and data pipelines using a few lines of YAML.', + icon: Boxes, + href: '/docs/category/providers', + cta: 'Explore providers', + }, + { + title: 'Developer-first workflows', + description: + 'Preview, ship, and roll back from the CLI while Defang manages infrastructure, secrets, and observability.', + icon: Terminal, + href: '/docs/category/cli', + cta: 'See CLI guides', + }, + { + title: 'AI-compatible by default', + description: + 'Spin up AI agents, flows, and scheduled jobs with batteries-included examples and best practices.', + icon: Sparkles, + href: '/docs/category/use-cases', + cta: 'Build an agent', + }, +]; + +const timeline = [ + { + title: 'Get started in minutes', + description: + 'Follow the quickstart to install the CLI, authenticate, and deploy your first service without touching the console.', + href: '/docs/getting-started', + }, + { + title: 'Scale to production', + description: + 'Define infrastructure once and promote through environments with declarative blueprints and guardrails built in.', + href: '/docs/category/concepts', + }, + { + title: 'Automate everything', + description: + 'Leverage managed pipelines, build triggers, and cloud events so your team ships reliable software every day.', + href: '/docs/category/tutorials', + }, +]; export default function Home() { - useEffect(() => { - window.location.href = '/docs'; - }, []); + return ( + +
+
+
+
+
+ + Built for calm, cloud-native shipping + + + Updated {new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })} + +
+

+ Shipping calm cloud software is finally the default +

+

+ Defang abstracts the noisy parts of infrastructure so you can focus on features. + These docs walk through the workflows, architecture patterns, and reference deployments that power modern teams. +

+
+ + +
+
+ {['AWS', 'GCP', 'Docker', 'Pulumi', 'OpenAI'].map((logo) => ( +
+ {logo} +
+ ))} +
+
+
+ +
+ {featureCards.map(({ title, description, icon: Icon, href, cta }) => ( + + +
+
+ {title} + {description} +
+ + + +
+ ))} +
+ +
+
+
+ + Guided track + +

+ A documentation experience that mirrors the way you ship +

+

+ Move from hello world to production in a straight line. Each stage links to runnable samples, infrastructure definitions, and launch checklists. +

+
+ {timeline.map(({ title, description, href }) => ( + +
+
+
+

{title}

+

{description}

+
+ + ))} +
+
+
+
+

What’s inside

+
    +
  • + ✅ Environment-aware deploy workflows with automatic rollbacks +
  • +
  • + ✅ Infrastructure blueprints for web apps, workers, and agents +
  • +
  • + ✅ Samples that pair CLI commands with production-grade configs +
  • +
+ +
+
+
- return null; +
+
+ + Recently refreshed + +

+ New launches, walkthroughs, and templates every month +

+

+ Stay in the flow with product updates, migration notes, and real-world sample apps built by the Defang community. +

+
+
+ {[ + { + title: 'Deploy your first agent with Crew AI', + href: '/blog/2025/06/16/crew-ai-sample', + description: 'Use Defang workflows to bootstrap retrieval-augmented agents that scale beyond experiments.', + }, + { + title: 'May 2025 product highlights', + href: '/blog/2025/06/06/may-product-updates', + description: 'Ship features faster with environment-aware deploys, project templates, and richer metrics.', + }, + { + title: 'Opinionated Docker Compose to Defang migration', + href: '/blog/2025/06/16/docker-compose-defang', + description: 'A guided path for teams standardizing on Defang after years running compose in production.', + }, + ].map(({ title, href, description }) => ( + + + {title} + {description} + + + + + + ))} +
+
+
+
+ ); } diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000000..79038694fa --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,88 @@ +const { fontFamily } = require('tailwindcss/defaultTheme'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ['class', '[data-theme="dark"]'], + content: [ + './src/**/*.{js,jsx,ts,tsx,mdx}', + './docs/**/*.{md,mdx}', + './blog/**/*.{md,mdx}', + './docusaurus.config.js', + ], + theme: { + container: { + center: true, + padding: '1.5rem', + screens: { + '2xl': '1280px', + }, + }, + extend: { + fontFamily: { + sans: ['var(--font-sans)', ...fontFamily.sans], + display: ['var(--font-display)', ...fontFamily.sans], + }, + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--radix-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: '0' }, + }, + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + }, + boxShadow: { + 'glow': '0 20px 45px -20px hsla(var(--primary), 0.5)', + }, + backgroundImage: { + 'hero-grid': 'radial-gradient(circle at 20% 20%, hsla(var(--primary),0.2) 0, transparent 35%), radial-gradient(circle at 80% 0%, hsla(var(--primary),0.18) 0, transparent 40%), radial-gradient(circle at 50% 80%, hsla(var(--secondary),0.15) 0, transparent 45%)', + }, + }, + }, + plugins: [require('tailwindcss-animate')], +}; diff --git a/tests/visual/docs-ui.spec.ts b/tests/visual/docs-ui.spec.ts new file mode 100644 index 0000000000..eb435f0074 --- /dev/null +++ b/tests/visual/docs-ui.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Docs UI polish', () => { + test('code blocks use elevated surfaces in dark mode', async ({ page }) => { + await page.goto('/docs/getting-started'); + const background = await page.evaluate(() => { + const block = Array.from( + document.querySelectorAll('.theme-code-block') + ).find((el) => el.offsetParent !== null); + return block ? getComputedStyle(block).backgroundColor : null; + }); + expect(background === 'rgb(27, 38, 65)' || background === 'rgb(246, 248, 250)').toBeTruthy(); + }); + + test('tabs keep top-only rounding', async ({ page }) => { + await page.goto('/docs/getting-started'); + const radii = await page.evaluate(() => { + const tab = document.querySelector('.tabs__item'); + const active = document.querySelector( + '.tabs__item--active' + ); + if (!tab || !active) { + return null; + } + const inactiveStyles = getComputedStyle(tab); + const activeStyles = getComputedStyle(active); + return { + inactive: inactiveStyles.borderBottomLeftRadius, + active: activeStyles.borderBottomLeftRadius, + }; + }); + expect(radii).not.toBeNull(); + expect(radii?.inactive).toBe('0px'); + expect(radii?.active).toBe('0px'); + }); + + test('footer renders with brand gradient', async ({ page }) => { + await page.goto('/docs/getting-started'); + const background = await page.evaluate(() => { + const footer = document.querySelector('footer.footer--dark'); + return footer ? getComputedStyle(footer).backgroundImage : null; + }); + expect(background).not.toBeNull(); + expect(background).toContain('linear-gradient'); + expect(background).toContain('rgb(1, 29, 80)'); + expect(background).toContain('rgb(9, 62, 159)'); + }); +}); diff --git a/tests/visual/home.spec.ts b/tests/visual/home.spec.ts new file mode 100644 index 0000000000..f7c5bfbb27 --- /dev/null +++ b/tests/visual/home.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Landing visuals', () => { + test('hero renders with layered gradients and CTAs', async ({ page }) => { + await page.goto('/'); + const hero = page.getByTestId('landing-hero'); + await expect(hero).toBeVisible(); + + const heroImage = await hero.screenshot(); + expect(heroImage.byteLength).toBeGreaterThan(5000); + + const primaryCta = hero.locator('a', { hasText: 'Launch quickstart' }); + const backgroundImage = await primaryCta.evaluate( + (el) => getComputedStyle(el).backgroundImage + ); + expect(backgroundImage).toContain('linear-gradient'); + + const badge = hero.getByTestId('hero-badge'); + await expect(badge).toHaveText(/calm, cloud-native shipping/i); + }); + + test('feature grid showcases interactive cards', async ({ page }) => { + await page.goto('/'); + const grid = page.getByTestId('feature-grid'); + const cards = grid.getByTestId('feature-card'); + await expect(cards).toHaveCount(3); + + await cards.nth(0).hover(); + const hoveredShadow = await cards + .nth(0) + .evaluate((el) => getComputedStyle(el).boxShadow); + expect(hoveredShadow).not.toEqual('none'); + }); + + test('timeline section communicates progression', async ({ page }) => { + await page.goto('/'); + const steps = page.getByTestId('timeline').getByTestId('timeline-step'); + await expect(steps).toHaveCount(3); + await expect(steps.nth(1)).toContainText('Scale to production'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 314eab8a41..4404ca59a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,11 @@ // This file is not used in compilation. It is here just for a nice editor experience. "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ] + } } } From 7154fe01b875530cffa4ce13d85aea6ffb45bd69 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Wed, 1 Oct 2025 14:52:58 +0200 Subject: [PATCH 02/14] style: adjust heading sizes and fix breadcrumb icon display in docs theme --- src/css/custom.css | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/css/custom.css b/src/css/custom.css index 7bd14a1e22..745e30d588 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -207,6 +207,9 @@ .breadcrumbs__item--active { @apply text-sm font-medium text-muted-foreground hover:text-foreground; } + [class^="breadcrumbHomeIcon_node_modules-@docusaurus-theme-classic-lib-theme-DocBreadcrumbs-Items-Home-styles-module"] { + display: inline-block !important; + } .table-of-contents { @apply bg-card/60 backdrop-blur-sm border border-border/70 rounded-2xl px-4 py-4 text-sm shadow-sm; @@ -245,6 +248,30 @@ } } +.theme-doc-markdown h1 { + @apply text-5xl; +} + +.theme-doc-markdown h2 { + @apply text-3xl; +} + +.theme-doc-markdown h3 { + @apply text-2xl; +} + +.theme-doc-markdown h4 { + @apply text-xl; +} + +.theme-doc-markdown h5 { + @apply text-base; +} + +.theme-doc-markdown h6 { + @apply text-sm; +} + .tabs-container > div { /* rounded bottom corners */ From 4ec8975063373791a03144fab28aeb6e09ac4327 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Wed, 1 Oct 2025 15:29:00 +0200 Subject: [PATCH 03/14] spruce up the docs --- docs/intro/intro.mdx | 3 ++- src/css/custom.css | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/intro/intro.mdx b/docs/intro/intro.mdx index 51c8d9ab56..8d29106004 100644 --- a/docs/intro/intro.mdx +++ b/docs/intro/intro.mdx @@ -9,11 +9,12 @@ import DocCardList from "@docusaurus/theme-classic/lib/theme/DocCardList"; #  
- Defang Logo + Defang Logo

Defang Documentation

Defang lets you take your app from Docker Compose to a secure and scalable deployment on your favorite cloud in minutes. Build cloud applications in any language and stack, deploy to your account on your favorite cloud with a single command, and iterate quickly with AI-assisted tooling.

+

Getting Started

- - -
- {featureCards.map(({ title, description, icon: Icon, href, cta }) => ( - - -
-
- {title} - {description} -
- - - -
- ))} -
- -
-
-
- - Guided track - -

- A documentation experience that mirrors the way you ship -

-

- Move from hello world to production in a straight line. Each stage links to runnable samples, infrastructure definitions, and launch checklists. -

-
- {timeline.map(({ title, description, href }) => ( - -
-
-
-

{title}

-

{description}

-
- - ))} -
-
-
-
-

What’s inside

-
    -
  • - ✅ Environment-aware deploy workflows with automatic rollbacks -
  • -
  • - ✅ Infrastructure blueprints for web apps, workers, and agents -
  • -
  • - ✅ Samples that pair CLI commands with production-grade configs -
  • -
- -
-
-
- -
-
- - Recently refreshed - -

- New launches, walkthroughs, and templates every month -

-

- Stay in the flow with product updates, migration notes, and real-world sample apps built by the Defang community. -

-
-
- {[ - { - title: 'Deploy your first agent with Crew AI', - href: '/blog/2025/06/16/crew-ai-sample', - description: 'Use Defang workflows to bootstrap retrieval-augmented agents that scale beyond experiments.', - }, - { - title: 'May 2025 product highlights', - href: '/blog/2025/06/06/may-product-updates', - description: 'Ship features faster with environment-aware deploys, project templates, and richer metrics.', - }, - { - title: 'Opinionated Docker Compose to Defang migration', - href: '/blog/2025/06/16/docker-compose-defang', - description: 'A guided path for teams standardizing on Defang after years running compose in production.', - }, - ].map(({ title, href, description }) => ( - - - {title} - {description} - - - - - - ))} -
-
- - - ); } From b02b04a11a564a91a0912831e394897915a5c25c Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Wed, 1 Oct 2025 18:54:01 +0200 Subject: [PATCH 09/14] rm unnecessary tests --- tests/visual/docs-ui.spec.ts | 48 ------------------------------------ tests/visual/home.spec.ts | 41 ------------------------------ 2 files changed, 89 deletions(-) delete mode 100644 tests/visual/docs-ui.spec.ts delete mode 100644 tests/visual/home.spec.ts diff --git a/tests/visual/docs-ui.spec.ts b/tests/visual/docs-ui.spec.ts deleted file mode 100644 index eb435f0074..0000000000 --- a/tests/visual/docs-ui.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test.describe('Docs UI polish', () => { - test('code blocks use elevated surfaces in dark mode', async ({ page }) => { - await page.goto('/docs/getting-started'); - const background = await page.evaluate(() => { - const block = Array.from( - document.querySelectorAll('.theme-code-block') - ).find((el) => el.offsetParent !== null); - return block ? getComputedStyle(block).backgroundColor : null; - }); - expect(background === 'rgb(27, 38, 65)' || background === 'rgb(246, 248, 250)').toBeTruthy(); - }); - - test('tabs keep top-only rounding', async ({ page }) => { - await page.goto('/docs/getting-started'); - const radii = await page.evaluate(() => { - const tab = document.querySelector('.tabs__item'); - const active = document.querySelector( - '.tabs__item--active' - ); - if (!tab || !active) { - return null; - } - const inactiveStyles = getComputedStyle(tab); - const activeStyles = getComputedStyle(active); - return { - inactive: inactiveStyles.borderBottomLeftRadius, - active: activeStyles.borderBottomLeftRadius, - }; - }); - expect(radii).not.toBeNull(); - expect(radii?.inactive).toBe('0px'); - expect(radii?.active).toBe('0px'); - }); - - test('footer renders with brand gradient', async ({ page }) => { - await page.goto('/docs/getting-started'); - const background = await page.evaluate(() => { - const footer = document.querySelector('footer.footer--dark'); - return footer ? getComputedStyle(footer).backgroundImage : null; - }); - expect(background).not.toBeNull(); - expect(background).toContain('linear-gradient'); - expect(background).toContain('rgb(1, 29, 80)'); - expect(background).toContain('rgb(9, 62, 159)'); - }); -}); diff --git a/tests/visual/home.spec.ts b/tests/visual/home.spec.ts deleted file mode 100644 index f7c5bfbb27..0000000000 --- a/tests/visual/home.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test.describe('Landing visuals', () => { - test('hero renders with layered gradients and CTAs', async ({ page }) => { - await page.goto('/'); - const hero = page.getByTestId('landing-hero'); - await expect(hero).toBeVisible(); - - const heroImage = await hero.screenshot(); - expect(heroImage.byteLength).toBeGreaterThan(5000); - - const primaryCta = hero.locator('a', { hasText: 'Launch quickstart' }); - const backgroundImage = await primaryCta.evaluate( - (el) => getComputedStyle(el).backgroundImage - ); - expect(backgroundImage).toContain('linear-gradient'); - - const badge = hero.getByTestId('hero-badge'); - await expect(badge).toHaveText(/calm, cloud-native shipping/i); - }); - - test('feature grid showcases interactive cards', async ({ page }) => { - await page.goto('/'); - const grid = page.getByTestId('feature-grid'); - const cards = grid.getByTestId('feature-card'); - await expect(cards).toHaveCount(3); - - await cards.nth(0).hover(); - const hoveredShadow = await cards - .nth(0) - .evaluate((el) => getComputedStyle(el).boxShadow); - expect(hoveredShadow).not.toEqual('none'); - }); - - test('timeline section communicates progression', async ({ page }) => { - await page.goto('/'); - const steps = page.getByTestId('timeline').getByTestId('timeline-step'); - await expect(steps).toHaveCount(3); - await expect(steps.nth(1)).toContainText('Scale to production'); - }); -}); From f87259b0bffabcfd20f706acfd20962b18667daf Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Wed, 1 Oct 2025 19:02:02 +0200 Subject: [PATCH 10/14] update node in test build --- .github/workflows/test-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 2e0803f2ef..f55b6bc77f 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 20 cache: npm - name: Install dependencies From 402c14c5f3ca1cfc743cc87d742de1a44c6d4b84 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Thu, 2 Oct 2025 12:15:34 +0200 Subject: [PATCH 11/14] rm playwright --- .gitignore | 2 -- package-lock.json | 60 ------------------------------------- package.json | 1 - playwright.config.ts | 34 --------------------- src/theme/Layout/index.js | 39 ++++++++++++++++-------- test-results/.last-run.json | 4 +++ 6 files changed, 31 insertions(+), 109 deletions(-) delete mode 100644 playwright.config.ts create mode 100644 test-results/.last-run.json diff --git a/.gitignore b/.gitignore index 65db4d1b64..b0408bb9b2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,3 @@ defang samples /docs/samples.md /static/samples-v2.json -playwright-report -test-results diff --git a/package-lock.json b/package-lock.json index 8425106212..d3e8b0fe04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,6 @@ "@docusaurus/module-type-aliases": "3.0.0", "@docusaurus/tsconfig": "3.0.0", "@docusaurus/types": "3.0.0", - "@playwright/test": "^1.55.1", "@types/react": "^18.2.29", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", @@ -5269,21 +5268,6 @@ "node": ">=14" } }, - "node_modules/@playwright/test": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", - "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", - "dev": true, - "dependencies": { - "playwright": "1.55.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -15401,50 +15385,6 @@ "pathe": "^2.0.3" } }, - "node_modules/playwright": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", - "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", - "dev": true, - "dependencies": { - "playwright-core": "1.55.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", - "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", - "dev": true, - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/points-on-curve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", diff --git a/package.json b/package.json index 9f2374856a..78d562296f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "@docusaurus/module-type-aliases": "3.0.0", "@docusaurus/tsconfig": "3.0.0", "@docusaurus/types": "3.0.0", - "@playwright/test": "^1.55.1", "@types/react": "^18.2.29", "autoprefixer": "^10.4.21", "postcss": "^8.5.6", diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 51493d550a..0000000000 --- a/playwright.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -const PORT = process.env.PORT ? Number(process.env.PORT) : 3100; -const HOST = '127.0.0.1'; - -export default defineConfig({ - testDir: './tests', - timeout: 120_000, - expect: { - timeout: 10_000, - }, - fullyParallel: true, - retries: process.env.CI ? 2 : 0, - use: { - baseURL: `http://${HOST}:${PORT}`, - trace: 'retain-on-failure', - viewport: { width: 1440, height: 900 }, - colorScheme: 'dark', - }, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], - webServer: { - command: `DEFANG_SKIP_GENERATION=1 npm run start -- --host ${HOST} --port ${PORT} --no-open`, - url: `http://${HOST}:${PORT}`, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - stderr: 'pipe', - timeout: 120_000, - }, -}); diff --git a/src/theme/Layout/index.js b/src/theme/Layout/index.js index e14a19c239..1ef39ec7db 100644 --- a/src/theme/Layout/index.js +++ b/src/theme/Layout/index.js @@ -2,7 +2,24 @@ import { useLocation } from "@docusaurus/router"; import Layout from "@theme-original/Layout"; import React, { useEffect } from "react"; -let searchTrackingTimeout; +const debounce = (fn, delay) => { + let timeoutId; + const debounced = (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + fn(...args); + }, delay); + }; + debounced.cancel = () => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }; + return debounced; +}; export default function LayoutWrapper(props) { const location = useLocation(); @@ -24,24 +41,21 @@ export default function LayoutWrapper(props) { return undefined; } + const trackSearch = debounce((value) => { + window?.analytics?.track("docsSearch", { + searchQuery: value, + }); + }, 1000); + const handleChange = (event) => { - if (searchTrackingTimeout) { - clearTimeout(searchTrackingTimeout); - } - searchTrackingTimeout = setTimeout(() => { - window?.analytics?.track("docsSearch", { - searchQuery: event.target.value, - }); - }, 1000); + trackSearch(event.target.value); }; search.addEventListener("change", handleChange); return () => { search.removeEventListener("change", handleChange); - if (searchTrackingTimeout) { - clearTimeout(searchTrackingTimeout); - } + trackSearch.cancel(); }; }; @@ -74,3 +88,4 @@ export default function LayoutWrapper(props) { ); } + diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000000..cbcc1fbac1 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file From 79217ff1d0c3441fb1428b09947173ce4b73aa87 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Thu, 2 Oct 2025 12:15:54 +0200 Subject: [PATCH 12/14] rm test result dir --- test-results/.last-run.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test-results/.last-run.json diff --git a/test-results/.last-run.json b/test-results/.last-run.json deleted file mode 100644 index cbcc1fbac1..0000000000 --- a/test-results/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "passed", - "failedTests": [] -} \ No newline at end of file From d682234682899a577a066e071764e02a25bb7f4a Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Thu, 2 Oct 2025 12:17:49 +0200 Subject: [PATCH 13/14] rm all refs to testing and cleanup extra line --- scripts/prebuild.sh | 5 ----- src/pages/index.tsx | 1 - 2 files changed, 6 deletions(-) diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index 0da8c290d8..6717004dc7 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -2,11 +2,6 @@ set -e -if [ "$DEFANG_SKIP_GENERATION" = "1" ]; then - echo 'Skipping CLI and sample generation (DEFANG_SKIP_GENERATION=1)'; - exit 0 -fi - CWD=$(pwd) CLI_DOCS_PATH=$(readlink -f docs/cli) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 65085485a0..5419eaec55 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,3 @@ - import { useEffect } from 'react'; From 6bd571ff4d024ad10b736b4c3d74830847e48077 Mon Sep 17 00:00:00 2001 From: Raphael Titsworth-Morin Date: Thu, 2 Oct 2025 13:01:21 +0200 Subject: [PATCH 14/14] fix: update search tracking to use input event and add underline style to doc links --- src/css/custom.css | 4 ++++ src/theme/Layout/index.js | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/css/custom.css b/src/css/custom.css index b5bb2f1ea6..2c95703a17 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -388,6 +388,10 @@ img.unstyled { box-shadow: none !important; } +.theme-doc-markdown a { + @apply underline; +} + /** * Page Specific */ diff --git a/src/theme/Layout/index.js b/src/theme/Layout/index.js index 1ef39ec7db..4ee480f96a 100644 --- a/src/theme/Layout/index.js +++ b/src/theme/Layout/index.js @@ -33,9 +33,7 @@ export default function LayoutWrapper(props) { } const attachSearchHandler = () => { - const search = - window.document.getElementById("docsearch-input") ?? - window.document.getElementById("search_input_react"); + const search = window.document.getElementById("docsearch-input"); if (!search) { return undefined; @@ -51,10 +49,10 @@ export default function LayoutWrapper(props) { trackSearch(event.target.value); }; - search.addEventListener("change", handleChange); + search.addEventListener("input", handleChange); return () => { - search.removeEventListener("change", handleChange); + search.removeEventListener("input", handleChange); trackSearch.cancel(); }; }; @@ -88,4 +86,3 @@ export default function LayoutWrapper(props) { ); } -