diff --git a/apps/www/.gitignore b/apps/www/.gitignore index 48dce735..c0eb215e 100644 --- a/apps/www/.gitignore +++ b/apps/www/.gitignore @@ -39,3 +39,6 @@ lerna-debug.log* # Yarn .yarn/* !.yarn/releases + +# Cloudflare +functions/**/*.js diff --git a/apps/www/README.md b/apps/www/README.md index 482a769f..385e914d 100644 --- a/apps/www/README.md +++ b/apps/www/README.md @@ -63,3 +63,50 @@ The production build will generate client and server modules by running both cli ```shell bun build # or `bun build` ``` + +## Cloudflare Pages + +Cloudflare's [wrangler](https://github.com/cloudflare/wrangler) CLI can be used to preview a production build locally. To start a local server, run: + +``` +bun serve +``` + +Then visit [http://localhost:8787/](http://localhost:8787/) + +### Deployments + +[Cloudflare Pages](https://pages.cloudflare.com/) are deployable through their [Git provider integrations](https://developers.cloudflare.com/pages/platform/git-integration/). + +If you don't already have an account, then [create a Cloudflare account here](https://dash.cloudflare.com/sign-up/pages). Next go to your dashboard and follow the [Cloudflare Pages deployment guide](https://developers.cloudflare.com/pages/framework-guides/deploy-anything/). + +Within the projects "Settings" for "Build and deployments", the "Build command" should be `bun build`, and the "Build output directory" should be set to `dist`. + +### Function Invocation Routes + +Cloudflare Page's [function-invocation-routes config](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes) can be used to include, or exclude, certain paths to be used by the worker functions. Having a `_routes.json` file gives developers more granular control over when your Function is invoked. +This is useful to determine if a page response should be Server-Side Rendered (SSR) or if the response should use a static-site generated (SSG) `index.html` file. + +By default, the Cloudflare pages adaptor _does not_ include a `public/_routes.json` config, but rather it is auto-generated from the build by the Cloudflare adaptor. An example of an auto-generate `dist/_routes.json` would be: + +``` +{ + "include": [ + "/*" + ], + "exclude": [ + "/_headers", + "/_redirects", + "/build/*", + "/favicon.ico", + "/manifest.json", + "/service-worker.js", + "/about" + ], + "version": 1 +} +``` + +In the above example, it's saying _all_ pages should be SSR'd. However, the root static files such as `/favicon.ico` and any static assets in `/build/*` should be excluded from the Functions, and instead treated as a static file. + +In most cases the generated `dist/_routes.json` file is ideal. However, if you need more granular control over each path, you can instead provide you're own `public/_routes.json` file. When the project provides its own `public/_routes.json` file, then the Cloudflare adaptor will not auto-generate the routes config and instead use the committed one within the `public` directory. diff --git a/apps/www/adapters/cloudflare-pages/vite.config.ts b/apps/www/adapters/cloudflare-pages/vite.config.ts new file mode 100644 index 00000000..56cd3d92 --- /dev/null +++ b/apps/www/adapters/cloudflare-pages/vite.config.ts @@ -0,0 +1,15 @@ +import { cloudflarePagesAdapter } from "@builder.io/qwik-city/adapters/cloudflare-pages/vite"; +import { extendConfig } from "@builder.io/qwik-city/vite"; +import baseConfig from "../../vite.config"; + +export default extendConfig(baseConfig, () => { + return { + build: { + ssr: true, + rollupOptions: { + input: ["src/entry.cloudflare-pages.tsx", "@qwik-city-plan"], + }, + }, + plugins: [cloudflarePagesAdapter()], + }; +}); diff --git a/apps/www/package.json b/apps/www/package.json index fc838185..b3c3ac68 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -15,20 +15,25 @@ "build": "qwik build", "build.client": "vite build", "build.preview": "vite build --ssr src/entry.preview.tsx", + "build.server": "vite build -c adapters/cloudflare-pages/vite.config.ts", "build.types": "tsc --incremental --noEmit", - "deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'", + "deploy": "wrangler pages deploy ./dist", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", "fmt": "prettier --write .", "fmt.check": "prettier --check .", "lint": "eslint \"src/**/*.ts*\"", "preview": "qwik build preview && vite preview --open", + "serve": "wrangler pages dev ./dist --compatibility-flags=nodejs_als", "start": "vite --open --mode ssr", "qwik": "qwik" }, "devDependencies": { "@builder.io/qwik": "^1.8.0", "@builder.io/qwik-city": "^1.8.0", + "@nestri/eslint-config": "workspace:*", + "@nestri/typescript-config": "workspace:*", + "@nestri/ui": "workspace:*", "@types/eslint": "8.56.10", "@types/node": "20.14.11", "@typescript-eslint/eslint-plugin": "7.16.1", @@ -39,6 +44,7 @@ "typescript": "5.4.5", "undici": "*", "vite": "5.3.5", - "vite-tsconfig-paths": "^4.2.1" + "vite-tsconfig-paths": "^4.2.1", + "wrangler": "^3.0.0" } } diff --git a/apps/www/postcss.config.cjs b/apps/www/postcss.config.cjs new file mode 100644 index 00000000..e4cb5c3d --- /dev/null +++ b/apps/www/postcss.config.cjs @@ -0,0 +1 @@ +module.exports = require("@nestri/ui/postcss"); \ No newline at end of file diff --git a/apps/www/public/_headers b/apps/www/public/_headers new file mode 100644 index 00000000..76367924 --- /dev/null +++ b/apps/www/public/_headers @@ -0,0 +1,9 @@ +# https://developers.cloudflare.com/pages/platform/headers/ + +/*service-worker.js + Cache-Control: no-store + Content-Type: application/javascript + X-Content-Type-Options: nosniff + +/build/* + Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable diff --git a/apps/www/public/_redirects b/apps/www/public/_redirects new file mode 100644 index 00000000..e2746108 --- /dev/null +++ b/apps/www/public/_redirects @@ -0,0 +1 @@ +# https://developers.cloudflare.com/pages/platform/redirects/ diff --git a/apps/www/src/entry.cloudflare-pages.tsx b/apps/www/src/entry.cloudflare-pages.tsx new file mode 100644 index 00000000..510a6309 --- /dev/null +++ b/apps/www/src/entry.cloudflare-pages.tsx @@ -0,0 +1,24 @@ +/* + * WHAT IS THIS FILE? + * + * It's the entry point for Cloudflare Pages when building for production. + * + * Learn more about the Cloudflare Pages integration here: + * - https://qwik.dev/docs/deployments/cloudflare-pages/ + * + */ +import { + createQwikCity, + type PlatformCloudflarePages, +} from "@builder.io/qwik-city/middleware/cloudflare-pages"; +import qwikCityPlan from "@qwik-city-plan"; +import { manifest } from "@qwik-client-manifest"; +import render from "./entry.ssr"; + +declare global { + interface QwikCityPlatform extends PlatformCloudflarePages {} +} + +const fetch = createQwikCity({ render, qwikCityPlan, manifest }); + +export { fetch }; diff --git a/apps/www/src/global.css b/apps/www/src/global.css deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/www/src/root.tsx b/apps/www/src/root.tsx index 3e42ec38..93409a78 100644 --- a/apps/www/src/root.tsx +++ b/apps/www/src/root.tsx @@ -7,7 +7,7 @@ import { import { RouterHead } from "./components/router-head/router-head"; import { isDev } from "@builder.io/qwik/build"; -import "./global.css"; +import "@nestri/ui/globals.css"; export default component$(() => { /** diff --git a/apps/www/src/routes/index.tsx b/apps/www/src/routes/index.tsx index 4fb7a52a..0df099fe 100644 --- a/apps/www/src/routes/index.tsx +++ b/apps/www/src/routes/index.tsx @@ -3,14 +3,9 @@ import type { DocumentHead } from "@builder.io/qwik-city"; export default component$(() => { return ( - <> -

Hi 👋

-
- Can't wait to see what you build with qwik! -
- Happy coding. -
- +
+ +
); }); diff --git a/apps/www/tailwind.config.js b/apps/www/tailwind.config.js new file mode 100644 index 00000000..22d269ed --- /dev/null +++ b/apps/www/tailwind.config.js @@ -0,0 +1,11 @@ +// import colors from "tailwindcss/colors"; +import baseConfig from "@nestri/ui/tailwind.config"; +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./{src,components,app}/**/*.{ts,tsx,html}", + "../../packages/ui/src/**/*.{ts,tsx}", + ], + presets: [baseConfig], + plugins: [], +}; \ No newline at end of file diff --git a/apps/www/tsconfig.json b/apps/www/tsconfig.json index 639f88e8..8ee25804 100644 --- a/apps/www/tsconfig.json +++ b/apps/www/tsconfig.json @@ -21,5 +21,5 @@ } }, "files": ["./.eslintrc.cjs"], - "include": ["src", "./*.d.ts", "./*.config.ts"] + "include": ["src", "./*.d.ts", "./*.config.ts", "./*.config.js", "postcss.config.cjs"] } diff --git a/bun.lockb b/bun.lockb index e11c8ca1..bffc2693 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index b791e7d0..d93e7221 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,15 +5,21 @@ "files": [ "library.js", "next.js", - "react-internal.js" + "react-internal.js", + "qwik.js" ], "devDependencies": { "@vercel/style-guide": "^5.2.0", "eslint-config-turbo": "^2.0.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-only-warn": "^1.1.0", - "@typescript-eslint/parser": "^7.1.0", - "@typescript-eslint/eslint-plugin": "^7.1.0", - "typescript": "^5.3.3" + "@types/eslint": "8.56.10", + "@types/node": "20.14.11", + "@typescript-eslint/eslint-plugin": "7.16.1", + "@typescript-eslint/parser": "7.16.1", + "eslint": "8.57.0", + "eslint-plugin-qwik": "^1.8.0", + "prettier": "3.3.3", + "typescript": "5.4.5" } } diff --git a/packages/eslint-config/qwik.js b/packages/eslint-config/qwik.js new file mode 100644 index 00000000..8aac57a1 --- /dev/null +++ b/packages/eslint-config/qwik.js @@ -0,0 +1,42 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:qwik/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + ecmaVersion: 2021, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + plugins: ["@typescript-eslint"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/ban-ts-comment": "off", + "prefer-spread": "off", + "no-case-declarations": "off", + "no-console": "off", + "@typescript-eslint/no-unused-vars": ["error"], + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + }, + }; + \ No newline at end of file diff --git a/packages/ui/globals.css b/packages/ui/globals.css new file mode 100644 index 00000000..cefe6043 --- /dev/null +++ b/packages/ui/globals.css @@ -0,0 +1,223 @@ +@tailwind components; +@tailwind base; +@tailwind utilities; + + +@layer base { + + html, + html * { + scrollbar-color: theme("colors.gray.700") theme("colors.gray.300"); + scrollbar-width: thin; + } + + *::selection { + background-color: theme("colors.primary.200"); + } + + *::-moz-selection { + background-color: theme("colors.primary.200"); + } + + html.dark *::selection { + background-color: theme("colors.primary.800"); + } + + html.dark *::-moz-selection { + background-color: theme("colors.primary.800"); + } + + html.dark, + html.dark * { + scrollbar-color: theme("colors.gray.300") theme("colors.gray.700"); + scrollbar-width: thin; + } + + @media (prefers-color-scheme: dark) { + *::selection { + background-color: theme("colors.primary.800"); + } + + *::-moz-selection { + background-color: theme("colors.primary.800"); + } + + html, + html * { + scrollbar-color: theme("colors.gray.300") theme("colors.gray.700"); + scrollbar-width: thin; + } + } + + input:-webkit-autofill, + input:-webkit-autofill:focus { + transition: background-color 0s 600000s, color 0s 600000s !important; + } + +} + +.marquee-container { + width: 100%; + padding: 1rem; + scale: var(--scale); +} + +.marquee-container[data-spill=true]::after { + --padding-x: 1rem; + --padding-y: 1rem; + content: ""; + position: fixed; + top: 50%; + left: 50%; + background: hsl(10 80% 0% / 0.25); + width: calc(var(--scale) * 10000vw); + height: calc(var(--scale) * 10000vh); + pointer-events: none; + translate: -50% -50%; + mask: + linear-gradient(white, white) 50% 50% / 100% 100% no-repeat, + linear-gradient(white, white) 50% 50% / calc(100cqi + (var(--padding-x) * 2)) calc(100cqh + (var(--padding-y) * 2)) no-repeat; + mask-composite: exclude; +} + +.marquee-container:not([data-spill=true]) { + overflow: hidden; +} + +[data-direction=horizontal] { + height: 100%; +} + +[data-direction=vertical] { + width: 100%; +} + +.marquee-container ul { + display: flex; + padding: 0; + margin: 0; + list-style-type: none; +} + +[data-reverse=true] * { + animation-direction: reverse !important; +} + +[data-translate=track][data-direction=horizontal] ul { + --destination-x: -100%; + animation: track-translate calc(var(--speed) * 1s) infinite linear; +} + +[data-translate=track][data-direction=vertical] ul { + --destination-y: -100%; + animation: track-translate calc(var(--speed) * 1s) infinite linear; +} + +[data-translate=track][data-direction=horizontal][data-pad=true] ul { + --destination-x: calc((100% / -3) * 2); + translate: calc(100% / -3) 0; +} + +[data-translate=track][data-direction=vertical][data-pad=true] ul { + --destination-y: calc((100% / -3) * 2); + translate: 0 calc(100% / -3); +} + + +[data-pad-diff=true] .pad { + background: hsl(0 0% 10%); + color: hsl(0 0% 98%); +} + +@keyframes track-translate { + to { + translate: var(--destination-x, 0) var(--destination-y, 0); + } +} + +[data-direction=horizontal] ul { + height: max-content; + width: fit-content; + align-items: center; +} + +[data-direction=vertical] ul { + width: 100%; + height: fit-content; + justify-items: center; + flex-direction: column; +} + +/* .marquee-container ul li { + height: auto; + aspect-ratio: 4 / 3; + background: hsl(0 0% 90%); + border-radius: 6px; + font-size: clamp(2rem, 4vw + 1rem, 8rem); + display: grid; + place-items: center; + border: 1px solid hsl(0 0% 50%); +} */ + +[data-play-state=running] :is(ul, li) { + animation-play-state: running !important; +} + +[data-play-state=paused] :is(ul, li) { + animation-play-state: paused !important; +} + + +/* The animation stuff */ +@media(prefers-reduced-motion: no-preference) { + [data-translate=items] ul { + gap: 0; + } + + [data-translate=items][data-direction=horizontal].marquee-container { + padding-inline: 0; + } + + [data-translate=items][data-direction=vertical].marquee-container { + padding-block: 0; + } + + [data-translate=items][data-spill=true][data-direction=horizontal].marquee-container::after { + --padding-x: 0rem; + } + + [data-translate=items][data-direction=vertical][data-spill=true].marquee-container::after { + --padding-y: 0rem; + } + + [data-pause-on-hover=true]:hover li { + animation-play-state: paused !important; + } + + [data-translate=items] li { + --duration: calc(var(--speed) * 1s); + --delay: calc((var(--duration) / var(--count)) * (var(--index, 0) - (var(--count) * 0.5))); + animation: slide var(--duration) calc(var(--delay) - (var(--count) * 0.5s)) infinite linear paused; + translate: var(--origin-x) var(--origin-y); + } + + [data-translate=items][data-direction=horizontal] li { + --origin-x: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%); + --origin-y: 0; + --destination-x: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%)); + --destination-y: 0; + } + + [data-translate=items][data-direction=vertical] li { + --origin-x: 0; + --origin-y: calc(((var(--count) - var(--index)) + var(--inset, 0)) * 100%); + --destination-x: 0; + --destination-y: calc(calc((var(--index) + 1 + var(--outset, 0)) * -100%)); + } + + @keyframes slide { + 100% { + translate: var(--destination-x) var(--destination-y); + } + } +} \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index aed73da2..c5e6e762 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -5,24 +5,35 @@ "exports": { "./button": "./src/button.tsx", "./card": "./src/card.tsx", - "./code": "./src/code.tsx" + "./code": "./src/code.tsx", + "./tailwind.config": "./tailwind.config.js", + "./globals.css": "./globals.css", + "./postcss": "./post.config.js" }, + "files": ["tailwind.config.js", "post.config.js", "globals.css", "postcss.config.js"], "scripts": { "lint": "eslint . --max-warnings 0", "generate:component": "turbo gen react-component" }, "devDependencies": { + "@builder.io/qwik": "^1.8.0", + "@builder.io/qwik-react": "^0.5.5", "@nestri/eslint-config": "*", "@nestri/typescript-config": "*", "@turbo/gen": "^1.12.4", - "@types/node": "^20.11.24", "@types/eslint": "^8.56.5", - "@types/react": "^18.2.61", - "@types/react-dom": "^18.2.19", + "@types/node": "^20.11.24", + "@types/react": "^18.3.4", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.20", "eslint": "^8.57.0", + "framer-motion": "^11.3.31", + "postcss": "^8.4.41", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.2", + "tailwind-variants": "^0.2.1", + "tailwindcss": "^3.4.10", "typescript": "^5.3.3" - }, - "dependencies": { - "react": "^18.2.0" } } diff --git a/packages/ui/post.config.js b/packages/ui/post.config.js new file mode 100644 index 00000000..4701c1c1 --- /dev/null +++ b/packages/ui/post.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js new file mode 100644 index 00000000..909ae0d9 --- /dev/null +++ b/packages/ui/tailwind.config.js @@ -0,0 +1,134 @@ +import colors from "tailwindcss/colors"; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + './{src,components,app}/**/*.{ts,tsx,html}', + ], + theme: { + extend: { + colors: { + primary: { + 50: "#FFEDE5", + 100: "#FFDBCC", + 200: "#FFB899", + 300: "#FF9466", + 400: "#FF7033", + 500: "#FF4F01", + 600: "#CC3D00", + 700: "#992E00", + 800: "#661F00", + 900: "#330F00", + 950: "#190800", + DEFAULT: "#FF4F01" + }, + secondary: colors.orange, + accent: colors.amber, + gray: { + ...colors.neutral, + 925: "#111111", + }, + danger: colors.red, + warning: colors.yellow, + success: colors.green, + info: colors.blue, + }, + fontFamily: { + body: [ + "Geist Sans", + "ui-sans-serif", + "system-ui", + "-apple-system", + "BlinkMacSystemFont", + "Inter", + "Segoe UI", + "Roboto", + "sans-serif", + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji", + ], + title: [ + "Bricolage Grotesque", + "-apple-system", + "blinkmacsystemfont", + "segoe ui", + "roboto", + "oxygen", + "ubuntu", + "cantarell", + "fira", + "sans", + "droid sans", + "helvetica neue", + "sans-serif", + ] + }, + boxShadow: { + "shadow-floating-light": "0px 6px 12px 0px rgba(99,99,99,.06),0px 22px 22px 0px rgba(99,99,99,.05),0px 50px 30px 0px rgba(99,99,99,.03),0px 89px 36px 0px rgba(99,99,99,.01),0px 140px 39px 0px rgba(99,99,99,0)", + "inner-shadow-dark-float": "0px 1px 0px 0px hsla(0,0%,100%,.03) inset,0px 0px 0px 1px hsla(0,0%,100%,.03) inset,0px 0px 0px 1px rgba(0,0,0,.1),0px 2px 2px 0px rgba(0,0,0,.1),0px 4px 4px 0px rgba(0,0,0,.1),0px 8px 8px 0px rgba(0,0,0,.1)", + "fullscreen": "0 0 0 1px rgba(0,0,0,.08),0px 1px 1px rgba(0,0,0,.02),0px 8px 16px -4px rgba(0,0,0,.04),0px 24px 32px -8px rgba(0,0,0,.06)", + "menu": "0 0 0 1px rgba(0,0,0,.08),0px 1px 1px rgba(0,0,0,.02),0px 4px 8px -4px rgba(0,0,0,.04),0px 16px 24px -8px rgba(0,0,0,.06)", + }, + keyframes: { + "fade-up": { + "0%": { + opacity: "0", + transform: "translateY(10px)", + }, + "80%": { + opacity: "0.7", + }, + "100%": { + opacity: "1", + transform: "translateY(0px)", + }, + }, + "fade-down": { + "0%": { + opacity: "0", + transform: "translateY(-10px)", + }, + "80%": { + opacity: "0.6", + }, + "100%": { + opacity: "1", + transform: "translateY(0px)", + }, + }, + shake: { + "25%": { + translate: "0.25ch 0" + }, + "75%": { + translate: "-0.25ch 0" + } + }, + slide: { + "100%": { + translate: "var(--destination-x) var(--destination-y);", + }, + }, + "multicolor": { + "0%": { + transform: "translateX(0%)" + }, + "100%": { + transform: "translateX(-50%)" + } + } + }, + animation: { + // Fade up and down + "fade-up": "fade-up 0.5s", + "fade-down": "fade-down 0.5s", + "shake": "shake 0.075s 8", + "multicolor": "multicolor 5s linear 0s infinite" + }, + }, + plugins: [] + } +} + diff --git a/packages/ui/tsconfig.lint.json b/packages/ui/tsconfig.lint.json index 5d86484e..25daed22 100644 --- a/packages/ui/tsconfig.lint.json +++ b/packages/ui/tsconfig.lint.json @@ -3,6 +3,6 @@ "compilerOptions": { "outDir": "dist" }, - "include": ["src", "turbo"], + "include": ["src", "tailwind.config.js", "post.config.js"], "exclude": ["node_modules", "dist"] }