diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b9d514b..a6cc386 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,3 @@ -# 🚀 Pull Request - ## Type of Change diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index cd9c324..d43ee29 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -58,7 +58,7 @@ const preview: Preview = { return isDualMode ? ( {Story(context.args, context)} ) : ( -
+
{Story(context.args, context)}
); diff --git a/README.md b/README.md index dd9a9d3..12c1fee 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Qupid comes with pre-built sections that you can use to create your landing page | Steps | Section with title, subtitle, and multiple steps (numbered icons with title and description). | | FAQ | Section with title, subtitle, and multiple questions with answers. | | Terminal | Section with title, subtitle, and terminal-like code block with optional prompt. | +| Video | Section with title, subtitle, and embedded video player. | > [!TIP] > See [Storybook](https://kungfux.github.io/qupid/storybook/) for live demo of each section and how to configure them or use [Stackblitz](https://stackblitz.com/github/kungfux/qupid?file=configuration.yaml) to play with the configuration. @@ -252,6 +253,7 @@ jobs: twitter: icon: fab fa-twitter href: # + ``` @@ -339,7 +341,7 @@ site: Use `sections` block to define which sections should display and in which order. -Each section has it's own configuration entries.Most of the elements are optional. Omit property declaration in the configuration if you don't need a section to display particular element. +Each section has it's own configuration entries. Most of the elements are optional. Omit property declaration in the configuration if you don't need a section to display particular element. ```yml sections: @@ -466,7 +468,7 @@ Here are some common special characters and how to handle them: - `` ` `` (backtick) - Use quotes if the backtick is part of a string value. Example: ```code: "Here is some `inline code` in a sentence."``` or use backslash to escape it: ```code: Here is some \`inline code\` in a sentence.```. + Use quotes if the backtick is part of a string value. Example: ``code: "Here is some `inline code` in a sentence."`` or use backslash to escape it: ``code: Here is some \`inline code\` in a sentence.``. ## 🤝 Feedback and Contributions diff --git a/configuration.yaml b/configuration.yaml index 4a427c8..d1ded1e 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -189,6 +189,14 @@ sections: description: Integrate with popular analytics providers like [Google Analytics](https://analytics.google.com/) and others to track your website's performance. icon: fas fa-chart-line + video: + type: video + title: Watch Qupid in Action + subtitle: See how easy it is to create a landing page with Qupid. + video: + src: https://www.youtube.com/embed/xz5jZ1zwFC4 + ratio: 1.7777777777778 # 16 / 9 aspect ratio + build_now: type: hero title: Build Now diff --git a/package-lock.json b/package-lock.json index d0eeaa7..51a3181 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^7.1.0", "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-aspect-ratio": "^1.1.8", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.3", @@ -114,6 +115,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1324,6 +1326,70 @@ } } }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz", + "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "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/@radix-ui/react-collapsible": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", @@ -2797,6 +2863,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -3038,6 +3105,7 @@ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -3047,6 +3115,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3057,6 +3126,7 @@ "integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3130,6 +3200,7 @@ "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -3388,6 +3459,7 @@ "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", @@ -3525,6 +3597,7 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -3583,6 +3656,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3818,6 +3892,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -4254,6 +4329,7 @@ "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4331,6 +4407,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9331,6 +9408,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9768,6 +9846,7 @@ "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.56.0" }, @@ -9946,6 +10025,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9987,6 +10067,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10397,6 +10478,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -10587,6 +10669,7 @@ "integrity": "sha512-4+U7gF9hMpGilQmdVJwQaVZZEkD7XwC4ZDmBa51mobaPYelELEMoMfNM2hLyvB2x12gk1IJui1DnwOE4t+MXhw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@testing-library/jest-dom": "^6.6.3", @@ -11119,6 +11202,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11399,6 +11483,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -11533,6 +11618,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -11792,6 +11878,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 99a8ce2..d61b83b 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,13 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "storybook": "storybook dev -p 6006", + "storybook": "storybook dev -p 6006 --no-open", "build-storybook": "storybook build" }, "dependencies": { "@fortawesome/fontawesome-free": "^7.1.0", "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-aspect-ratio": "^1.1.8", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.3", diff --git a/src/configuration/types/sections/video-section.ts b/src/configuration/types/sections/video-section.ts new file mode 100644 index 0000000..99fcd65 --- /dev/null +++ b/src/configuration/types/sections/video-section.ts @@ -0,0 +1,15 @@ +import type { RepeatableSection } from './repeatable-section'; + +export const VIDEO_SECTION_TYPE = 'video' as const; + +export interface VideoItem { + src: string; + ratio?: number; +} + +export interface VideoSection extends RepeatableSection { + type?: typeof VIDEO_SECTION_TYPE; + title?: string; + subtitle?: string; + video: VideoItem; +} diff --git a/src/sections.tsx b/src/sections.tsx index 9dadbba..0addab2 100644 --- a/src/sections.tsx +++ b/src/sections.tsx @@ -19,11 +19,16 @@ import { TERMINAL_SECTION_TYPE, type TerminalSection, } from './configuration/types/sections/terminal-section'; +import { + VIDEO_SECTION_TYPE, + type VideoSection, +} from './configuration/types/sections/video-section'; import Cards from './ui/sections/cards'; import Faq from './ui/sections/faq'; import Hero from './ui/sections/hero'; import Steps from './ui/sections/steps'; import Terminal from './ui/sections/terminal'; +import Video from './ui/sections/video'; export default function Sections({ sections }: Record) { return ( @@ -97,6 +102,19 @@ export default function Sections({ sections }: Record) { ); } + case VIDEO_SECTION_TYPE: { + const video = section as VideoSection; + return ( +