diff --git a/examples/getting-started/.eslintrc.js b/examples/getting-started/.eslintrc.js new file mode 100644 index 0000000..cbbe2ca --- /dev/null +++ b/examples/getting-started/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + parserOptions: { + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + env: { + node: true, + es2021: true, + }, + extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'], + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + }, + settings: { + react: { + version: '18.1', + }, + }, +}; diff --git a/examples/getting-started/.gitignore b/examples/getting-started/.gitignore new file mode 100644 index 0000000..b1ecb93 --- /dev/null +++ b/examples/getting-started/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +hubspot.config.yml +dist diff --git a/examples/getting-started/.node-version b/examples/getting-started/.node-version new file mode 100644 index 0000000..8b0beab --- /dev/null +++ b/examples/getting-started/.node-version @@ -0,0 +1 @@ +20.11.0 diff --git a/examples/getting-started/.prettierrc b/examples/getting-started/.prettierrc new file mode 100644 index 0000000..dc75c8a --- /dev/null +++ b/examples/getting-started/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/examples/getting-started/README.md b/examples/getting-started/README.md new file mode 100644 index 0000000..235588a --- /dev/null +++ b/examples/getting-started/README.md @@ -0,0 +1,99 @@ +# Getting Started Example + +The getting started example within this directory covers a variety of key features/components of working with CMS React within a HubSpot website. This guide outlines the specifics of using CMS React primitives such as Modules, Fields, and Islands, as well as integrating third-party dependencies, styling, assets, and setting up local development tooling for your project. + +## Setup + +### Environment + +To set up your development environment to work with CMS React, you’ll need the following: + +- A HubSpot account with CMS Free, Starter, Pro, or Enterprise access +- Node.js v18.0 or higher and the associated version of NPM + +## Creating and Deploying a CMS React Project + +### 1. Clone the Repo + +First, clone the CMS React repository: + +```bash +git clone https://github.com/HubSpot/cms-react.git +``` + +CMS React projects have a 3 significant directories: + +- `getting-started-project`: The HubSpot project directory that gets uploaded via the Projects system to build and deploy your CMS React code found in the `/src` directory. +- `getting-started-project/src`: A package inside your project directory containing all your assets, styles, components, modules, islands, and any local development tooling you need, such as ESLint, Prettier, TypeScript, etc. +- `getting-started-theme`: This is where your HubL templates and theme configuration lives. + +At the root of this example, you will configure any local dev tooling you want, such as ESLint, Prettier, TypeScript, etc. + +### 2. Install Dependencies + +Navigate to the `cms-react/examples/getting-started` directory in your terminal and run: + +```bash +npm install +``` + +This will install all the local development tooling you need, including `@hubspot/cli`, `@hubspot/cms-dev-server`, and any other dev tooling you configured. + +### 3. Local Development + +To run this example project locally, navigate to the `getting-started-project/src` directory and run: + +```bash +npm run start +``` + +Then navigate to [http://hslocal.net:3000](http://hslocal.net:3000) to see an index page that links to all your modules. + +### 4. Uploading and Deploying on HubSpot + +Run `hs init` from the root of the repository to configure it to upload to your HubSpot account using your personal access key. When setting up JavaScript assets for the first time, you will need to deactivate and regenerate your personal access key, ensuring it includes `CMS Pages`, `Design Manager`, `Developer Projects`, and `GraphQL Data Fetching` permissions. This will create a `hubspot.config.yaml` file required for both uploading changes and local development. See the [HubSpot CLI documentation](https://developers.hubspot.com/docs/cms/guides/getting-started-with-local-development) for more information. + +> **Warning:** If you are using an existing access key from a previous call to `hs init` or `hs auth`, you will need to deactivate and regenerate the access key to include new scopes necessary for local CMS React development. + +Double-check you are still in `getting-started/` and run: + +```bash +npm run deploy +``` + +This will upload the `getting-started-project` to your HubSpot account. Once uploaded, built, and deployed, you can use your react modules in your website pages just as you would with any other modules. + +From the repository root, run: + +```bash +npm run upload:hubl +``` + +This uploads the corresponding example HubL files to your account. You should now be able to create a page from one of the `getting-started-theme` templates and see the output of the React components in the page preview. + +### 5. Previewing Local Changes on Proxied Pages + +You can preview your local CMS React components inside live HubL-rendered pages. To do this, create a page: + +- Go to Website Pages +- Click “Create”, then “Create Website Page" +- Write a page name and click “Create page” +- Switch your active theme to "CMS React - Getting Started Theme" and select one of the templates +- Name the page in the settings tab, then publish and preview it + +To proxy that page locally: + +- Run the local dev server as described above +- Visit the newly created page in your browser +- Open the sprocket menu and select `Open Local Dev Server` +- Your local JavaScript source changes will appear immediately in the context of the HubL page + +## Functionality + +This project includes modules, module fields, and islands. Modules are the primary building block of the HubSpot CMS, and Islands enable client-side interactivity for React-powered modules. We have 3 modules: + +- **Header**: A React module without islands. +- **Footer**: Another example of a React module without islands. +- **Weather**: A module leveraging our island architecture to fetch and render data from an API client-side, enabling users to change the data they wish to fetch, and formatting dates using a third-party dependency. + +In addition to the above, we are using module css to scope our styles to a given file. For more styling options, check out our [Styling example](https://github.com/HubSpot/cms-react/tree/main/examples/styling). diff --git a/examples/getting-started/getting-started-project/hsproject.json b/examples/getting-started/getting-started-project/hsproject.json new file mode 100644 index 0000000..7de2a73 --- /dev/null +++ b/examples/getting-started/getting-started-project/hsproject.json @@ -0,0 +1,5 @@ +{ + "name": "getting-started-project", + "srcDir": ".", + "platformVersion": "2023.2" +} diff --git a/examples/getting-started/getting-started-project/src/Globals.d.ts b/examples/getting-started/getting-started-project/src/Globals.d.ts new file mode 100644 index 0000000..60260a3 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/Globals.d.ts @@ -0,0 +1 @@ +declare module '*.module.css'; diff --git a/examples/getting-started/getting-started-project/src/assets/clear.svg b/examples/getting-started/getting-started-project/src/assets/clear.svg new file mode 100644 index 0000000..d192b7f --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/clear.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/cloudy.svg b/examples/getting-started/getting-started-project/src/assets/cloudy.svg new file mode 100644 index 0000000..283e3cc --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/cloudy.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/fog.svg b/examples/getting-started/getting-started-project/src/assets/fog.svg new file mode 100644 index 0000000..8431c61 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/fog.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/rain.svg b/examples/getting-started/getting-started-project/src/assets/rain.svg new file mode 100644 index 0000000..d96ac2f --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/rain.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/snow.svg b/examples/getting-started/getting-started-project/src/assets/snow.svg new file mode 100644 index 0000000..baf2f5f --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/snow.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/sprocket.svg b/examples/getting-started/getting-started-project/src/assets/sprocket.svg new file mode 100644 index 0000000..cb945b6 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/sprocket.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/assets/thunderstorm.svg b/examples/getting-started/getting-started-project/src/assets/thunderstorm.svg new file mode 100644 index 0000000..0f09655 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/assets/thunderstorm.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/getting-started/getting-started-project/src/cms-assets.json b/examples/getting-started/getting-started-project/src/cms-assets.json new file mode 100644 index 0000000..f7037b1 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/cms-assets.json @@ -0,0 +1,4 @@ +{ + "label": "Getting started", + "outputPath": "" +} diff --git a/examples/getting-started/getting-started-project/src/components/WeatherCards.tsx b/examples/getting-started/getting-started-project/src/components/WeatherCards.tsx new file mode 100644 index 0000000..d76b41b --- /dev/null +++ b/examples/getting-started/getting-started-project/src/components/WeatherCards.tsx @@ -0,0 +1,42 @@ +import dayjs from 'dayjs'; +import weatherStyles from '../styles/weather.module.css'; +import { getWeatherIcon } from '../utils.ts'; + +export function CurrentWeatherCard({ weatherData }: any) { + const { location, forecast } = weatherData; + const { forecastday } = forecast; + const currentDay = forecastday[0].day; + + return ( +
+
+ {currentDay.condition.text} +

+ {currentDay.avgtemp_f}° + F +

+
+

{location.name}

+ {location.country} +
+ ); +} + +export function UpcomingWeatherCard({ weatherData }: any) { + return ( +
+ {dayjs(weatherData.date).format('dddd')} + {weatherData.day.condition.text} +

+ {weatherData.day.avgtemp_f}° + F +

+
+ ); +} diff --git a/examples/getting-started/getting-started-project/src/components/islands/WeatherForecast.tsx b/examples/getting-started/getting-started-project/src/components/islands/WeatherForecast.tsx new file mode 100644 index 0000000..63d57b2 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/components/islands/WeatherForecast.tsx @@ -0,0 +1,85 @@ +import { useEffect, useState } from 'react'; +import weatherStyles from '../../styles/weather.module.css'; +import { getWeatherForecast } from '../../utils.ts'; +import { CurrentWeatherCard, UpcomingWeatherCard } from '../WeatherCards.tsx'; + +type WeatherCondition = { + text: string; + icon: string; + code: number; +}; + +type DailyForecast = Record; + +type Day = { + day: Record; +}; + +type Forecast = { + forecastday: DailyForecast[]; +}; + +export type CurrentWeather = Record; +type Location = Record; + +export type WeatherData = { + location: Location; + current: CurrentWeather; + forecast: Forecast; +}; + +type WeatherForecastProps = { + headline: string; + defaultCity: string; +}; + +export default function WeatherForecast({ + headline, + defaultCity, +}: WeatherForecastProps) { + const [city, setCity] = useState(''); + const [weatherData, setWeatherData] = useState(); + + useEffect(() => { + getWeatherForecast(defaultCity).then((data) => { + setWeatherData(JSON.parse(data as string)); + }); + }, []); + + const handleFetchWeather = () => { + getWeatherForecast(city).then((data) => { + setWeatherData(JSON.parse(data as string)); + }); + }; + + return ( +
+

{headline}

+
+ setCity(event.target.value)} + /> + +
+ {weatherData?.forecast ? ( + <> +
+ +
+
+ {weatherData.forecast.forecastday.map((weather: any, i) => { + if (i === 0) return null; + return ( + + ); + })} +
+ + ) : ( +

No data found, please search another location

+ )} +
+ ); +} diff --git a/examples/getting-started/getting-started-project/src/components/modules/Footer/index.tsx b/examples/getting-started/getting-started-project/src/components/modules/Footer/index.tsx new file mode 100644 index 0000000..3f1ee0c --- /dev/null +++ b/examples/getting-started/getting-started-project/src/components/modules/Footer/index.tsx @@ -0,0 +1,20 @@ +import { ModuleFields, TextField } from '@hubspot/cms-components/fields'; +import footerStyles from '../../../styles/footer.module.css'; + +export function Component({ fieldValues }: any) { + return ( +
+

{fieldValues.footerText}

+
+ ); +} + +export const fields = ( + + + +); + +export const meta = { + label: 'Footer Module', +}; diff --git a/examples/getting-started/getting-started-project/src/components/modules/Header/index.tsx b/examples/getting-started/getting-started-project/src/components/modules/Header/index.tsx new file mode 100644 index 0000000..2d99c37 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/components/modules/Header/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Menu, logInfo } from '@hubspot/cms-components'; +import { + ImageField, + MenuField, + ModuleFields, +} from '@hubspot/cms-components/fields'; +import logo from '../../../assets/sprocket.svg'; +import headerStyles from '../../../styles/header.module.css'; + +export function Component({ fieldValues }: any) { + const { src, alt, width, height } = fieldValues.logo; + + return ( +
+ +
+ ); +} + +const DEFAULT_MENU_ID = ' + + + +); + +export const meta = { + label: 'Header Module', +}; diff --git a/examples/getting-started/getting-started-project/src/components/modules/Weather/index.tsx b/examples/getting-started/getting-started-project/src/components/modules/Weather/index.tsx new file mode 100644 index 0000000..fac9948 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/components/modules/Weather/index.tsx @@ -0,0 +1,28 @@ +import { Island } from '@hubspot/cms-components'; +import WeatherForecast from '../../islands/WeatherForecast.tsx?island'; +import { ModuleFields, TextField } from '@hubspot/cms-components/fields'; + +export function Component({ fieldValues }: any) { + return ( + + ); +} + +export const fields = ( + + + + +); + +export const meta = { + label: 'Weather Module', +}; diff --git a/examples/getting-started/getting-started-project/src/constants.ts b/examples/getting-started/getting-started-project/src/constants.ts new file mode 100644 index 0000000..13259cc --- /dev/null +++ b/examples/getting-started/getting-started-project/src/constants.ts @@ -0,0 +1,15 @@ +import CLEAR from './assets/clear.svg'; +import RAIN from './assets/rain.svg'; +import SNOW from './assets/snow.svg'; +import THUNDERSTORM from './assets/thunderstorm.svg'; +import FOG from './assets/fog.svg'; +import CLOUDY from './assets/cloudy.svg'; + +export const ICON_MAP = { + CLEAR, + RAIN, + SNOW, + THUNDERSTORM, + FOG, + CLOUDY, +}; diff --git a/examples/getting-started/getting-started-project/src/package.json b/examples/getting-started/getting-started-project/src/package.json new file mode 100644 index 0000000..826edda --- /dev/null +++ b/examples/getting-started/getting-started-project/src/package.json @@ -0,0 +1,22 @@ +{ + "name": "getting-started-example-app", + "version": "0.1.0", + "type": "module", + "dependencies": { + "@hubspot/cms-components": "latest", + "dayjs": "^1.11.11", + "prop-types": "^15.8.1", + "react": "^18.1.0" + }, + "devDependencies": { + "@hubspot/cms-dev-server": "latest", + "@testing-library/react": "^13.4.0", + "@vitejs/plugin-react": "^2.1.0", + "jsdom": "^20.0.1", + "vitest": "^0.24.3" + }, + "scripts": { + "start": "hs-cms-dev-server .", + "test": "vitest" + } +} diff --git a/examples/getting-started/getting-started-project/src/styles/footer.module.css b/examples/getting-started/getting-started-project/src/styles/footer.module.css new file mode 100644 index 0000000..43dd42c --- /dev/null +++ b/examples/getting-started/getting-started-project/src/styles/footer.module.css @@ -0,0 +1,5 @@ +.footer { + text-align: center; + font-weight: bold; + font-size: 2em; +} diff --git a/examples/getting-started/getting-started-project/src/styles/header.module.css b/examples/getting-started/getting-started-project/src/styles/header.module.css new file mode 100644 index 0000000..584e8f9 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/styles/header.module.css @@ -0,0 +1,30 @@ +.wrapper { + width: 100%; + background-color: transparent; + + & nav { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; + padding: 15px 25px; + + & ul { + gap: 20px; + list-style: none; + margin: 0; + padding: 0; + display: flex; + + & li { + & a { + text-decoration: none; + color: #ff7a59; + &:hover { + color: #2d3e50; + } + } + } + } + } +} diff --git a/examples/getting-started/getting-started-project/src/styles/weather.module.css b/examples/getting-started/getting-started-project/src/styles/weather.module.css new file mode 100644 index 0000000..3b8b65e --- /dev/null +++ b/examples/getting-started/getting-started-project/src/styles/weather.module.css @@ -0,0 +1,142 @@ +:root { + --primary-color: #ff7a59; + --primary-color-dark: #e66e50; + --primary-color-med: #ffbcac; + --secondary-color: #dfe3eb; + --accent-color: #2d3e50; + --light-accent-color: #eaf0f6; +} + +.wrapper { + background-color: var(--accent-color); + min-height: 40vh; + padding: 50px; + font-family: sans-serif; + + & h2 { + text-align: center; + margin-bottom: 50px; + color: white; + font-size: 2rem; + } + + & input[type="text"] { + border-radius: 5px; + border: none; + padding: 10px 15px; + font-size: 16px; + background-color: var(--light-accent-color); + } + + & button { + border-radius: 5px; + border:none; + background-color: var(--primary-color); + color: white; + padding: 10px 12px; + transition: background-color 0.3s; + cursor: pointer; + font-size: 14px; + + &:hover { + background-color: var(--primary-color-dark); + transition: background-color 0.3s; + } + } + + & .form { + display: flex; + justify-content: center; + margin-top: 20px; + align-items: center; + gap: 10px; + margin-bottom: 50px; + } + + & .cardContainer { + display: flex; + gap: 20px; + justify-content: center; + align-items: flex-start; + max-width: 300px; + margin: auto; + } + + & .card { + background-color: var(--secondary-color); + padding: 10px; + border-radius: 5px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.2); + min-width: 165px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + & img { + height: 100px; + } + + & h3 { + margin: 0 auto 10px auto; + } + } + + & .current { + background-color: var(--primary-color); + padding: 0px 25px 25px; + border-radius: 5px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.2); + max-width: 350px; + margin:auto; + min-height: 400px; + margin-bottom: 50px; + display: flex; + justify-content: center; + flex-direction: column; + + & h2 { + margin: 0 auto 10px auto; + } + + & .city { + text-align: center; + color: white; + } + + & .country { + text-align: center; + color: var(--primary-color-med); + } + + & h3 { + text-align: center; + color: white; + font-size: 3.5rem; + margin: 0; + position: relative; + + & .unit { + font-size: 22px; + position: absolute; + left: 113.5px; + top: 31px; + color: var(--primary-color-med); + } + } + + & .condition { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + margin-bottom: 25px; + + & img { + width:200px + } + } + } +} diff --git a/examples/getting-started/getting-started-project/src/tsconfig.json b/examples/getting-started/getting-started-project/src/tsconfig.json new file mode 100644 index 0000000..49cf7c7 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "isolatedModules": true, + "jsx": "react-jsx", + "skipLibCheck": true, + "types": ["vite/client", "vitest/globals"] + }, + "include": ["components/**/*"], + "exclude": ["node_modules"] +} diff --git a/examples/getting-started/getting-started-project/src/utils.ts b/examples/getting-started/getting-started-project/src/utils.ts new file mode 100644 index 0000000..bd86557 --- /dev/null +++ b/examples/getting-started/getting-started-project/src/utils.ts @@ -0,0 +1,89 @@ +import { ICON_MAP } from './constants.ts'; + +export function getWeatherIcon(type: string) { + switch (type) { + case 'Sunny': + case 'Clear': + return ICON_MAP.CLEAR; + + case 'Partly Cloudy': + case 'Cloudy': + case 'Overcast': + return ICON_MAP.CLOUDY; + + case 'Mist': + case 'Patchy rain possible': + case 'Light drizzle': + case 'Light rain': + case 'Moderate rain at times': + case 'Moderate rain': + case 'Heavy rain at times': + case 'Heavy rain': + case 'Light rain shower': + case 'Moderate or heavy rain shower': + case 'Torrential rain shower': + case 'Patchy light rain': + case 'Freezing drizzle': + case 'Heavy freezing drizzle': + case 'Light freezing rain': + case 'Moderate or heavy freezing rain': + case 'Patchy rain nearby': + return ICON_MAP.RAIN; + + case 'Patchy snow possible': + case 'Light snow': + case 'Moderate snow': + case 'Heavy snow': + case 'Patchy light snow': + case 'Patchy moderate snow': + case 'Patchy heavy snow': + case 'Blizzard': + case 'Blowing snow': + case 'Light snow showers': + case 'Moderate or heavy snow showers': + case 'Patchy light snow with thunder': + case 'Moderate or heavy snow with thunder': + case 'Patchy sleet possible': + case 'Light sleet': + case 'Moderate or heavy sleet': + case 'Light sleet showers': + case 'Moderate or heavy sleet showers': + case 'Ice pellets': + case 'Light showers of ice pellets': + case 'Moderate or heavy showers of ice pellets': + return ICON_MAP.SNOW; + + case 'Thundery outbreaks possible': + case 'Patchy light rain with thunder': + case 'Moderate or heavy rain with thunder': + return ICON_MAP.THUNDERSTORM; + + case 'Fog': + case 'Freezing fog': + return ICON_MAP.FOG; + + default: + return ''; + } +} + +const apiKey = ''; + +const baseApiUrl = 'https://weatherapi-com.p.rapidapi.com/forecast.json'; +const options = { + method: 'GET', + headers: { + 'x-rapidapi-key': apiKey, + 'x-rapidapi-host': 'weatherapi-com.p.rapidapi.com', + }, +}; + +export async function getWeatherForecast(city: string) { + try { + const response = await fetch(`${baseApiUrl}?q=${city}&days=3`, options); + const result = await response.text(); + return result as string; + } catch (error) { + console.error(error); + } +} diff --git a/examples/getting-started/getting-started-theme/fields.json b/examples/getting-started/getting-started-theme/fields.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/examples/getting-started/getting-started-theme/fields.json @@ -0,0 +1 @@ +[] diff --git a/examples/getting-started/getting-started-theme/layouts/base.hubl.html b/examples/getting-started/getting-started-theme/layouts/base.hubl.html new file mode 100644 index 0000000..f3eff2a --- /dev/null +++ b/examples/getting-started/getting-started-theme/layouts/base.hubl.html @@ -0,0 +1,36 @@ + + + + + + {% if page_meta.html_title or pageTitle %} + {{ page_meta.html_title or pageTitle }} + {% endif %} + {% if brand_settings.primaryFavicon.src %} + + {% endif %} + + {{ standard_header_includes }} + + +
+ {% block header %} + {% module 'main header' path="@projects/getting-started-project/src/components/modules/Header.tsx" %} + {% endblock header %} + + {# The main-content ID is used for the navigation skipper in the header.html file. More information on the navigation skipper can be found here: https://github.com/HubSpot/cms-theme-boilerplate/wiki/Accessibility #} + + {% block body %} + {% endblock body %} + + {% block footer %} + {% module 'footer' path="@projects/getting-started-project/src/components/modules/Footer.tsx" %} + {% endblock footer %} +
+ + {# To see a full list of what is included via standard_footer_includes please reference this article: https://developers.hubspot.com/docs/cms/hubl/variables#required-page-template-variables #} + {{ standard_footer_includes }} + + diff --git a/examples/getting-started/getting-started-theme/theme.json b/examples/getting-started/getting-started-theme/theme.json new file mode 100644 index 0000000..3e4f466 --- /dev/null +++ b/examples/getting-started/getting-started-theme/theme.json @@ -0,0 +1,3 @@ +{ + "label": "CMS React - Getting Started Theme" +} diff --git a/examples/getting-started/getting-started-theme/weather.hubl.html b/examples/getting-started/getting-started-theme/weather.hubl.html new file mode 100644 index 0000000..0ac10c2 --- /dev/null +++ b/examples/getting-started/getting-started-theme/weather.hubl.html @@ -0,0 +1,14 @@ + +{% extends "./layouts/base.hubl.html" %} + +{% block body %} + + {% module + path="@projects/getting-started-project/app/components/modules/Weather.tsx", + %} + +{% endblock body %} diff --git a/examples/getting-started/package.json b/examples/getting-started/package.json new file mode 100644 index 0000000..84f0430 --- /dev/null +++ b/examples/getting-started/package.json @@ -0,0 +1,26 @@ +{ + "name": "getting-started-example", + "description": "Getting started with CMS-React", + "license": "Apache-2.0", + "devDependencies": { + "@hubspot/cli": "latest", + "@hubspot/prettier-plugin-hubl": "latest", + "eslint": "^8.24.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-react": "^7.31.10", + "prettier": "^2.7.1", + "yarpm": "^1.2.0" + }, + "scripts": { + "start": "cd getting-started-project/src && yarpm start", + "postinstall": "cd getting-started-project/src && yarpm install", + "lint:js": "eslint . --ext .js,.jsx", + "prettier": "prettier . --check", + "watch:hubl": "hs watch getting-started-theme getting-started-theme", + "upload:hubl": "hs upload getting-started-theme getting-started-theme", + "deploy": "hs project upload getting-started-project" + }, + "engines": { + "node": ">=16.0.0" + } +}