├── types ├── index.d.ts └── funkyImage.d.ts ├── example.env ├── public └── favicon.png ├── tsconfig.json ├── .storybook ├── preview.js └── main.js ├── app ├── utils │ ├── delay.ts │ ├── emdashed.ts │ ├── setCookie.ts │ ├── keysToCamelCase.ts │ ├── getCookie.ts │ ├── splitAtDash.ts │ └── buildVideoEmbedUrl.ts ├── error.vue ├── components │ ├── wp-block │ │ ├── core-list-item.vue │ │ ├── core-spacer.vue │ │ ├── core-columns.vue │ │ ├── core-heading.vue │ │ ├── core-list.vue │ │ ├── core-paragraph.vue │ │ ├── core-video.vue │ │ ├── core-column.vue │ │ ├── core-quote.vue │ │ ├── core-image.vue │ │ ├── core-embed.vue │ │ └── core-gallery.vue │ ├── global-header.vue │ ├── wp-menu-item.vue │ ├── wp-menu.vue │ ├── wp-seo.vue │ ├── wp-image.vue │ └── wp-content.vue ├── layouts │ └── default.vue ├── plugins │ ├── init.ts │ └── browser.client.ts ├── assets │ ├── css │ │ ├── media.css │ │ ├── vars.css │ │ ├── main.css │ │ └── transitions.css │ └── svgs │ │ └── logo-funkhaus.svg ├── composables │ └── useWpFetch.ts ├── pages │ ├── [slug].vue │ ├── about.vue │ ├── directors.vue │ └── index.vue ├── app.vue ├── middleware │ └── redirect-trailing-slash.global.ts └── stores │ └── site.ts ├── .gitignore ├── .vscode └── settings.json ├── eslint.config.mjs ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ └── component-request.md ├── package.json ├── TODO-FUXT.md ├── README.md └── nuxt.config.ts /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './funkyImage' 2 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | WORDPRESS_API_URL = "https://fuxt-backend.funkhaus.us/wp-json/fuxt/v1/" -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu/fuxt/master/public/favicon.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /types/funkyImage.d.ts: -------------------------------------------------------------------------------- 1 | export type FunkyImageProps = { 2 | src: string 3 | mediaDetails?: { 4 | width: number 5 | height: number 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | const preview = { 2 | parameters: { 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/i 7 | } 8 | } 9 | } 10 | } 11 | 12 | export default preview 13 | -------------------------------------------------------------------------------- /app/utils/delay.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Used as an async/await friendly alternative to setTimeout 3 | */ 4 | function delay(time: number = 0): Promise { 5 | return new Promise((res) => { 6 | setTimeout(res, time) 7 | }) 8 | } 9 | 10 | export default delay -------------------------------------------------------------------------------- /app/error.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.useFlatConfig": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit", 5 | "source.organizeImports": "never" 6 | }, 7 | "eslint.codeActionsOnSave.mode": "all", 8 | "eslint.codeAction.showDocumentation": { 9 | "enable": true 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { createConfigForNuxt } from '@nuxt/eslint-config/flat' 2 | 3 | export default createConfigForNuxt({ 4 | features: { 5 | stylistic: { 6 | indent: 4, 7 | semi: false, 8 | commaDangle: 'never' 9 | }, 10 | typescript: true 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /app/utils/emdashed.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Replace hyphen with emdash 3 | */ 4 | function emdashed(value: string = ""): string { 5 | if (!value) { 6 | return "" 7 | } 8 | 9 | // Replace hyphen with emdash if value is a string 10 | if (typeof value === "string" || value instanceof String) { 11 | value = value.replace(/ – /g, " — ").replace(/ - /g, " — ") 12 | } 13 | 14 | return value 15 | } 16 | 17 | export default emdashed 18 | -------------------------------------------------------------------------------- /app/components/wp-block/core-list-item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /app/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /app/components/wp-block/core-spacer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('storybook-vue').StorybookConfig } */ 2 | const config = { 3 | stories: [ 4 | '../pages/**/*.stories.@(js|jsx|ts|tsx|mdx)', 5 | '../components/**/*.stories.@(js|jsx|ts|tsx|mdx)' 6 | ], 7 | addons: [ 8 | '@storybook/addon-links', 9 | '@storybook/addon-essentials', 10 | '@chromatic-com/storybook' 11 | ], 12 | framework: { 13 | name: '@storybook-vue/nuxt', 14 | options: {} 15 | }, 16 | docs: { 17 | autodocs: 'tag' 18 | } 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /app/components/global-header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /app/utils/setCookie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Set a cookie. Used by custom video player. 3 | * SEE https://stackoverflow.com/a/24103596/503546 4 | */ 5 | function setCookie(name: string, value: string, days?: number): void { 6 | let expires = "" 7 | if (days) { 8 | const date = new Date() 9 | date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000) 10 | expires = "; expires=" + date.toUTCString() 11 | } 12 | document.cookie = `${name}=${value || ""}${expires}; path=/` 13 | } 14 | 15 | export default setCookie 16 | -------------------------------------------------------------------------------- /app/utils/keysToCamelCase.ts: -------------------------------------------------------------------------------- 1 | // Convert object keys to camelCase 2 | // TODO Fix typescript errors in this 3 | function keysToCamelCase(obj: unknown): unknown { 4 | if (Array.isArray(obj)) { 5 | return obj.map(v => keysToCamelCase(v)); 6 | } else if (obj != null && obj.constructor === Object) { 7 | return Object.keys(obj).reduce( 8 | (result, key) => ({ 9 | ...result, 10 | [_CamelCase(key)]: keysToCamelCase(obj[key]), 11 | }), 12 | {}, 13 | ); 14 | } 15 | return obj; 16 | } 17 | 18 | export default keysToCamelCase 19 | -------------------------------------------------------------------------------- /app/plugins/init.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtPlugin(async () => { 2 | const siteStore = useSiteStore() 3 | if (siteStore.hasLoaded) { 4 | return 5 | } 6 | 7 | // Populate the store with the settings from the WP API 8 | await useSiteStore().init() 9 | 10 | // Configure NuxtLink defaults 11 | defineNuxtLink({ 12 | activeClass: 'active-link', 13 | exactActiveClass: 'exact-active-link', 14 | prefetch: true, 15 | trailingSlash: 'append', 16 | prefetchedClass: 'prefetched-link' 17 | }) 18 | 19 | siteStore.hasLoaded = true 20 | }) 21 | -------------------------------------------------------------------------------- /app/utils/getCookie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get a cookie. Used by custom video player. 3 | * SEE https://stackoverflow.com/a/24103596/503546 4 | */ 5 | function getCookie(name: string): string | null { 6 | const nameEQ = `${name}=` 7 | const cookiesArray = document.cookie.split(";") 8 | 9 | for (let i = 0; i < cookiesArray.length; i++) { 10 | let cookie = cookiesArray[i].trim() 11 | if (cookie.indexOf(nameEQ) === 0) { 12 | return cookie.substring(nameEQ.length, cookie.length) 13 | } 14 | } 15 | 16 | return null 17 | } 18 | 19 | export default getCookie 20 | -------------------------------------------------------------------------------- /app/assets/css/media.css: -------------------------------------------------------------------------------- 1 | @custom-media --small-viewport (max-width: 600px); 2 | @custom-media --medium-viewport (min-width: 601px) and (max-width: 1200px); 3 | @custom-media --large-viewport (min-width: 1201px); 4 | 5 | @custom-media --gt-cinema only screen and (min-width: 1800px); 6 | @custom-media --lt-cinema only screen and (max-width: 1799px); 7 | @custom-media --lt-tablet only screen and (max-width: 1024px); 8 | @custom-media --lt-phone only screen and (max-width: 850px); 9 | /* @custom-media --phone-landscape: 'only screen and (max-width: 850px) and (orientation: landscape)'; */ 10 | /* @custom-media --pad: "only screen and (min-device-width : 768px) and (max-device-width : 1024px)"; */ 11 | @custom-media --has-hover (hover: hover); 12 | 13 | 14 | 15 | /* Usage example: 16 | @media (--lt-phone) { 17 | background-color: red; 18 | } 19 | */ -------------------------------------------------------------------------------- /app/composables/useWpFetch.ts: -------------------------------------------------------------------------------- 1 | // Fetch from WP, parse response to camelCase object and return ref 2 | export function useWpFetch(endpoint: string, options: object = {}) { 3 | const baseURL = useRuntimeConfig().public.wordpressApiUrl 4 | 5 | const response = useFetch(endpoint, { 6 | transform: (data) => { 7 | return keysToCamelCase(data || {}) 8 | }, 9 | onRequest({ options }) { 10 | const { enabled } = usePreviewMode() 11 | 12 | // Add credentials to fetch request if preview enabled 13 | if (enabled.value) { 14 | options.credentials = 'include' 15 | options.headers = { ...options.headers, Preview: query.preview } 16 | } 17 | }, 18 | baseURL, 19 | ...options 20 | }) 21 | 22 | return response 23 | } 24 | -------------------------------------------------------------------------------- /app/pages/[slug].vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 39 | -------------------------------------------------------------------------------- /app/utils/splitAtDash.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Splits a string by the separator and optionally keeps the separator in the result. 3 | * Used primarily for separating text at an emdash (—). 4 | */ 5 | // TODO: refactor this function to use a regular expression instead of a string for separator 6 | function splitAtDash(text: string = "", separator: string = " — ", keepSeparator: boolean = false): string[] { 7 | let output = text.split(separator); 8 | 9 | // Add separator back into the array 10 | // This is useful if separating by an opening quote 11 | if (keepSeparator) { 12 | output = output.map((element, index) => { 13 | if (index > 0) { 14 | return ` ${separator} ` + element; 15 | } 16 | return element; 17 | }); 18 | } 19 | 20 | return output; 21 | } 22 | 23 | export default splitAtDash; 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Component Created:** {path and filename}.vue from #{issue number} 2 | 3 | **Stories:** {story filename}.stories.js 4 | 5 | **Imports:** 6 | 7 | - {list any imported components or libraries} 8 | 9 | **Notes:** 10 | 11 | {Any notes about what you built. How does it work? Anything missing?} 12 | 13 | **Time Report:** 14 | 15 | This took me {x} hours to build this. 16 | 17 | **Checklist:** 18 | 19 | - [ ] I double checked it looks like the designs 20 | - [ ] I completed any required mobile breakpoint styling 21 | - [ ] I completed any required hover state styling 22 | - [ ] I included a working Storybook file 23 | - [ ] I included a Story that showed some edge case working correctly (long text, short screen, missing image etc.) 24 | - [ ] I added notes above about how long it took to build this component 25 | - [ ] I assigned this PR to someone to review 26 | -------------------------------------------------------------------------------- /app/pages/about.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /app/pages/directors.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /app/assets/css/vars.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-white: #ffffff; 3 | --color-black: #000000; 4 | 5 | --unit-100vh: 100vh; 6 | --unit-gap: 40px; 7 | --unit-max-width: 1800px; 8 | 9 | --font-primary: sans-serif; 10 | --font-secondary: serif; 11 | 12 | --easing-authentic-motion: cubic-bezier(0.4, 0, 0.2, 1); 13 | 14 | /* Breakpoints */ 15 | @media (--lt-phone) { 16 | --unit-gap: 20px; 17 | } 18 | 19 | /* Handle 100vh on mobile using Dynamic View Height units */ 20 | @supports (height: 100dvh) { 21 | --unit-100vh: 100dvh; 22 | } 23 | } 24 | 25 | /* Set Breakpoint name, this is saved into Pina */ 26 | :root { 27 | --breakpoint-name: "desktop"; 28 | @media (--gt-cinema) { 29 | --breakpoint-name: "cinema"; 30 | } 31 | @media (--lt-cinema) { 32 | --breakpoint-name: "desktop"; 33 | } 34 | @media (--lt-tablet) { 35 | --breakpoint-name: "tablet"; 36 | } 37 | @media (--lt-phone) { 38 | --breakpoint-name: "phone"; 39 | } 40 | } -------------------------------------------------------------------------------- /app/app.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/components/wp-menu-item.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 39 | -------------------------------------------------------------------------------- /app/middleware/redirect-trailing-slash.global.ts: -------------------------------------------------------------------------------- 1 | import { withTrailingSlash, stringifyQuery } from 'ufo' 2 | 3 | export default defineNuxtRouteMiddleware((to, from) => { 4 | const pathEndsWithSlash = to.path.endsWith('/') 5 | let redirectUrl = withTrailingSlash(to.path) 6 | 7 | switch (true) { 8 | case to.path == '/': 9 | // The home page 10 | return 11 | break 12 | 13 | case to.fullPath.includes('sitemap.xml'): 14 | return 15 | break 16 | 17 | case to.path.includes('.'): 18 | // This is most likly a request to a specific file, like filename.png, 19 | // Thus we don't want to add a trailingslash. Storybook does this with iframe.html 20 | return 21 | break 22 | 23 | case !pathEndsWithSlash: 24 | // Add a trailing slash always 25 | redirectUrl = withTrailingSlash(to.path) 26 | if (to.query) { 27 | redirectUrl = `${redirectUrl}?${stringifyQuery(to.query)}` 28 | } 29 | return navigateTo(redirectUrl, { redirectCode: 301 }) 30 | break 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fuxt", 3 | "version": "4.0.0", 4 | "private": true, 5 | "type": "module", 6 | "engines": { 7 | "node": "22.x" 8 | }, 9 | "scripts": { 10 | "build": "nuxt build", 11 | "dev": "nuxt dev", 12 | "generate": "nuxt generate", 13 | "preview": "nuxt preview", 14 | "postinstall": "nuxt prepare", 15 | "storybook": "STORYBOOK=true storybook dev -p 6006", 16 | "build-storybook": "STORYBOOK=true storybook build" 17 | }, 18 | "dependencies": { 19 | "nuxt": "^3.13.2" 20 | }, 21 | "devDependencies": { 22 | "@csstools/postcss-global-data": "^3.0.0", 23 | "@nuxt/fonts": "^0.10.0", 24 | "@nuxtjs/sitemap": "6.0.1", 25 | "@nuxtjs/storybook": "^8.2.0", 26 | "@pinia/nuxt": "^0.5.1", 27 | "@storybook/addon-essentials": "^8.3.0", 28 | "@storybook/addon-links": "^8.3.0", 29 | "@storybook/vue3": "^8.3.0", 30 | "@types/node": "^22.5.5", 31 | "@vueuse/core": "^11.1.0", 32 | "@vueuse/nuxt": "^11.1.0", 33 | "nuxt-lodash": "^2.5.3", 34 | "nuxt-svgo": "^4.0.6", 35 | "pinia": "^2.1.7", 36 | "postcss": "^8.4.47", 37 | "postcss-custom-media": "^11.0.1", 38 | "postcss-nested": "^6.2.0", 39 | "storybook": "^8.3.0", 40 | "vue": "^3.4.36" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 57 | -------------------------------------------------------------------------------- /app/assets/svgs/logo-funkhaus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/components/wp-block/core-columns.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | -------------------------------------------------------------------------------- /TODO-FUXT.md: -------------------------------------------------------------------------------- 1 | ## TODO - Fuxt 2 | - SEO defaults 3 | - Port over default CSS vars 4 | - Remove GQL features 5 | - Get GA plugin working 6 | - Make sure Preview's work 7 | - Get nuxt-fonts working one day! 8 | - Maybe this helps with better Gutenberg support now: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-serialization-default-parser/ 9 | - Get some sort of template and story snippit working: 10 | - https://marketplace.visualstudio.com/items?itemName=exer7um.vue-3-vscode-snippets 11 | - https://medium.com/@tbusser/vue-and-storybook-component-scaffolding-50df77b8d79d 12 | 13 | ## TODO - Fuxt backend 14 | - Update plugin manifest 15 | - Add https://wordpress.org/plugins/wp-openapi/ 16 | - Auto favicon step from WP-Easy into fuxt-backend 17 | - Hide WP Open API menu item for non-Devs 18 | 19 | ## Questions for Conrawl 20 | - See GitHub issues on fuxt repo 21 | - How to fetch more data from a watcher? $fetch vs useWpFetch()? 22 | 23 | ## TODO - Base Components 24 | - WpImage 25 | - TODO Background color 26 | - TODO Focal points and color 27 | - TODO Only load video when coming into view 28 | - TODO Has-loaded of video? 29 | - [x] WpMenu 30 | - VideoStage 31 | - Gutenberg (LLM help?) 32 | - [x] WpSeo 33 | - WpControls 34 | - [x] WpLink (No longer needed actually, nuxt-link does it all now) 35 | - SplitText 36 | 37 | ## Usage Notes 38 | - Install VS Code plugin "ESlint" 39 | - Document vueUse and lodash auto import usage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuxt 2 | 3 | A complete Headless WordPress tech stack built on Nuxt 4. 4 | 5 | Works best with the [fuxt-backend](https://github.com/funkhaus/fuxt-backend) WordPress theme and included WordPress optimized components. 6 | 7 | Built by [Funkhaus](http://funkhaus.us/). We normally host on [Flywheel](https://share.getf.ly/n02x5z). 8 | 9 | PS: The name Fuxt comes from [Funkhaus](https://funkhaus.us) and Nuxt. [It's provocative](https://www.youtube.com/watch?v=_eRRab36XLI). 10 | 11 | ## Features 12 | 13 | - TODO 14 | 15 | ## Build Setup 16 | 17 | **This is just a [Nuxt site](https://nuxtjs.org), so it builds and deploys like any other Nuxt project.** 18 | 19 | Works best with the [fuxt-backend](https://github.com/funkhaus/fuxt-backend) WordPress theme as the backend. 20 | 21 | **First step:** Duplicate and rename `.example.env` to `.env`. Define any vars environment needed there. 22 | 23 | ```bash 24 | # install dependencies 25 | $ npm install 26 | 27 | # serve with hot reload at localhost:3000 28 | $ npm run dev 29 | 30 | # serve with hot reload Storybook at localhost:3003 31 | $ npm run storybook 32 | 33 | # build for production and launch server 34 | $ npm run build 35 | $ npm start 36 | 37 | # build Storybook for production 38 | $ npx nuxt storybook build 39 | 40 | # generate static project 41 | $ npm run generate 42 | 43 | ``` 44 | 45 | ## Documentation 46 | 47 | For detailed explanation on how things work, checkout [the wiki](https://github.com/funkhaus/fuxt/wiki). 48 | -------------------------------------------------------------------------------- /app/components/wp-block/core-heading.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | -------------------------------------------------------------------------------- /app/plugins/browser.client.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtPlugin(() => { 2 | const siteStore = useSiteStore() 3 | 4 | // Get browser dimensions 5 | const { y } = useWindowScroll() 6 | const { width: newWinWidth, height: newWinHeight } = useWindowSize() 7 | const newBreakpoint = useCssVar('--breakpoint-name') 8 | 9 | // Update scroll direction 10 | watch(y, (newVal, oldVal = 0) => { 11 | siteStore.sTop = newVal 12 | siteStore.breakpoint = newBreakpoint?.value?.replace(/['"]+/g, '').trim() || 'desktop' 13 | 14 | if (oldVal > newVal || newVal === 0) { 15 | siteStore.scrollDirection = 'up' 16 | } 17 | else { 18 | siteStore.scrollDirection = 'down' 19 | } 20 | }, { 21 | immediate: true 22 | }) 23 | 24 | // Update window dimensions 25 | watch([newWinWidth, newWinHeight], () => { 26 | siteStore.winWidth = newWinWidth.value 27 | siteStore.winHeight = newWinHeight.value 28 | }, { 29 | immediate: true 30 | }) 31 | 32 | // Update store referrer. Useful to know where the user came from. 33 | addRouteMiddleware('referrer', async (to, from) => { 34 | if (from.name) { 35 | siteStore.referrer = { 36 | name: from.name, 37 | fullPath: from.fullPath, 38 | path: from.path, 39 | query: from.query, 40 | params: from.params 41 | } 42 | } 43 | }, { global: true }) 44 | }) 45 | -------------------------------------------------------------------------------- /app/components/wp-block/core-list.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 47 | 48 | -------------------------------------------------------------------------------- /app/components/wp-block/core-paragraph.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | -------------------------------------------------------------------------------- /app/stores/site.ts: -------------------------------------------------------------------------------- 1 | export const useSiteStore = defineStore('site', () => { 2 | const settings = ref({}) 3 | const menuOpened = ref(false) 4 | const breakpoint = ref('desktop') 5 | const referrer: Ref = ref(false) 6 | const scrollDirection = ref('up') 7 | const sTop = ref(0) 8 | const winHeight = ref(0) 9 | const winWidth = ref(0) 10 | const hasLoaded = ref(false) 11 | 12 | // Setup default store settings values 13 | settings.value = { 14 | title: '', 15 | description: '', 16 | backendUrl: '', 17 | frontendUrl: '', 18 | themeScreenshotUrl: '', 19 | sociaMedia: [], 20 | googleAnalytics: [], 21 | socialSharedImage: {} 22 | } 23 | 24 | // Populate state from WP API 25 | const init = async () => { 26 | // Do requests in parallel 27 | const settingsReq = useWpFetch('/settings') 28 | const acfReq = useWpFetch('/acf-options?name=Site Options') 29 | const [settingsRes, acfRes] = await Promise.all([settingsReq, acfReq]) 30 | 31 | // Get ref values 32 | const settingsData = settingsRes.data?.value || {} 33 | const acfData = acfRes.data?.value || {} 34 | 35 | // Save to store 36 | settings.value = { ...settingsData, ...acfData } 37 | } 38 | 39 | return { 40 | settings, 41 | menuOpened, 42 | breakpoint, 43 | sTop, 44 | winHeight, 45 | winWidth, 46 | referrer, 47 | scrollDirection, 48 | hasLoaded, 49 | init 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /app/components/wp-block/core-video.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/component-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Component Request 3 | about: Use this to define a component that a developer can build. 4 | title: Component Request - {ComponentName} - {x}hrs 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Component Description 10 | 11 | This component is used {where} to display {what}. Be sure to explain any variants or hover states. 12 | 13 | ## Design 14 | 15 | Please also see attached screenshots for quick reference. 16 | 17 | - Desktop: https://xd.adobe.com/view/1234-5678/ 18 | - Mobile: https://xd.adobe.com/view/1234-5678/ 19 | 20 | If no mobile designs provided, please use your best judgment for responsiveness. 21 | 22 | ## Slots 23 | 24 | Name and description of any slots needed. 25 | 26 | ## Props 27 | 28 | ``` 29 | props: { 30 | exampleObject: { 31 | // Mock: api.page 32 | type: Object, 33 | default: () => ({}) 34 | }, 35 | exampleArray: { 36 | // Mock: api.pages 37 | type: Array, 38 | default: () => [] 39 | }, 40 | exampleNumber: { 41 | type: Number, 42 | default: 0 43 | }, 44 | exampleString: { 45 | type: String, 46 | default: "" 47 | }, 48 | exampleBoolean: { 49 | type: Boolean, 50 | default: true 51 | } 52 | } 53 | ``` 54 | 55 | ## Developer Tips 56 | 57 | List any developer tips here 58 | 59 | 1. `--color-example` for the font color 60 | 61 | ## Events 62 | 63 | Describe any events that should be emitted by this component. 64 | 65 | 1. `menuOpened` when {something} is clicked on 66 | 67 | ## Required components 68 | 69 | List out any components that are used by this new component. For example, if you are building a grid that is made up of block components. 70 | 71 | 1. `required-component` is used for {what} 72 | 73 | ## Screenshots 74 | 75 | {attach screenshots} 76 | -------------------------------------------------------------------------------- /app/components/wp-block/core-column.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | -------------------------------------------------------------------------------- /app/components/wp-block/core-quote.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 47 | 48 | -------------------------------------------------------------------------------- /app/components/wp-menu.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 78 | -------------------------------------------------------------------------------- /app/components/wp-seo.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 72 | 73 | 78 | -------------------------------------------------------------------------------- /app/components/wp-block/core-image.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 46 | 47 | -------------------------------------------------------------------------------- /app/components/wp-block/core-embed.vue: -------------------------------------------------------------------------------- 1 |