├── .codesandbox
└── tasks.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
└── workflows
│ ├── deploy-docs.yml
│ ├── deploy-test.yml
│ └── smoke-test.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── .node-version
├── .nojekyll
├── .vitepress
│ └── config.ts
├── index.md
├── package.json
├── release-log.md
└── yarn.lock
└── examples
├── .gitignore
├── contact-profile
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── contact-profile-project
│ ├── .hsignore
│ ├── contact-profile-app
│ │ ├── .node-version
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ ├── Column.jsx
│ │ │ ├── Company.jsx
│ │ │ ├── FormInput.jsx
│ │ │ ├── Grid.jsx
│ │ │ ├── Layout.jsx
│ │ │ ├── Link.jsx
│ │ │ ├── ProfileImage.jsx
│ │ │ ├── ProfileName.jsx
│ │ │ ├── Row.jsx
│ │ │ ├── SVGs.jsx
│ │ │ └── modules
│ │ │ │ ├── ContactProfile
│ │ │ │ ├── fields.jsx
│ │ │ │ └── index.jsx
│ │ │ │ └── ContactWithAssociations
│ │ │ │ ├── fields.jsx
│ │ │ │ └── index.jsx
│ │ ├── package.json
│ │ └── styles
│ │ │ ├── layout.module.css
│ │ │ └── theme.module.css
│ └── hsproject.json
├── contact-profile-theme
│ ├── contact-profile.hubl.html
│ ├── contact-with-associations.hubl.html
│ ├── fields.json
│ ├── layouts
│ │ └── base.hubl.html
│ └── theme.json
├── jsconfig.json
└── package.json
├── data-fetching
├── .eslintrc.js
├── .gitignore
├── .node-version
├── .prettierrc
├── LICENSE
├── data-fetching-project
│ ├── .hsignore
│ ├── data-fetching-app
│ │ ├── Globals.d.ts
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ ├── PokeCard.tsx
│ │ │ └── modules
│ │ │ │ └── Fetcher
│ │ │ │ ├── fields.tsx
│ │ │ │ └── index.tsx
│ │ ├── package.json
│ │ ├── styles
│ │ │ ├── component.module.css
│ │ │ └── pokecard.module.css
│ │ ├── tsconfig.json
│ │ ├── utils
│ │ │ └── index.ts
│ │ └── yarn.lock
│ └── hsproject.json
├── data-fetching-theme
│ ├── data-fetching.hubl.html
│ ├── fields.json
│ ├── layouts
│ │ └── base.hubl.html
│ └── theme.json
├── package.json
└── yarn.lock
├── getting-started-project-theme
├── .eslintrc.js
├── .gitignore
├── .node-version
├── .prettierrc
├── README.md
├── hsproject.json
├── package.json
└── src
│ └── getting-started-theme
│ ├── Globals.d.ts
│ ├── assets
│ ├── clear.svg
│ ├── cloudy.svg
│ ├── fog.svg
│ ├── rain.svg
│ ├── snow.svg
│ ├── sprocket.svg
│ └── thunderstorm.svg
│ ├── components
│ ├── WeatherCards.tsx
│ ├── islands
│ │ └── WeatherForecast.tsx
│ └── modules
│ │ ├── Footer
│ │ └── index.tsx
│ │ ├── Header
│ │ └── index.tsx
│ │ └── Weather
│ │ └── index.tsx
│ ├── constants.ts
│ ├── fields.json
│ ├── package.json
│ ├── styles
│ ├── footer.module.css
│ ├── header.module.css
│ └── weather.module.css
│ ├── templates
│ ├── layouts
│ │ └── base.hubl.html
│ └── weather.hubl.html
│ ├── theme.json
│ ├── tsconfig.json
│ └── utils.ts
├── getting-started
├── .eslintrc.js
├── .gitignore
├── .node-version
├── .prettierrc
├── README.md
├── getting-started-project
│ ├── getting-started-app
│ │ ├── Globals.d.ts
│ │ ├── assets
│ │ │ ├── clear.svg
│ │ │ ├── cloudy.svg
│ │ │ ├── fog.svg
│ │ │ ├── rain.svg
│ │ │ ├── snow.svg
│ │ │ ├── sprocket.svg
│ │ │ └── thunderstorm.svg
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ ├── WeatherCards.tsx
│ │ │ ├── islands
│ │ │ │ └── WeatherForecast.tsx
│ │ │ └── modules
│ │ │ │ ├── Footer
│ │ │ │ └── index.tsx
│ │ │ │ ├── Header
│ │ │ │ └── index.tsx
│ │ │ │ └── Weather
│ │ │ │ └── index.tsx
│ │ ├── constants.ts
│ │ ├── package.json
│ │ ├── styles
│ │ │ ├── footer.module.css
│ │ │ ├── header.module.css
│ │ │ └── weather.module.css
│ │ ├── tsconfig.json
│ │ └── utils.ts
│ └── hsproject.json
├── getting-started-theme
│ ├── fields.json
│ ├── layouts
│ │ └── base.hubl.html
│ ├── theme.json
│ └── weather.hubl.html
└── package.json
├── graphql-storybook
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── gql-storybook-project
│ ├── .hsignore
│ ├── gql-storybook-app
│ │ ├── .node-version
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ └── modules
│ │ │ │ └── ContactList
│ │ │ │ ├── ContactList.stories.jsx
│ │ │ │ ├── fields.tsx
│ │ │ │ └── index.tsx
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── vitest.config.js
│ └── hsproject.json
├── gql-storybook-theme
│ ├── fields.json
│ ├── gql-storybook.hubl.html
│ ├── layouts
│ │ └── base.hubl.html
│ └── theme.json
└── package.json
├── hello-world
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── hello-world-project
│ ├── .hsignore
│ ├── hello-world-app
│ │ ├── .node-version
│ │ ├── assets
│ │ │ ├── HubSpot-Sprocket.svg
│ │ │ ├── huayra.jpg
│ │ │ ├── orange-line.jpg
│ │ │ ├── tacoma.jpg
│ │ │ └── vw-id.jpg
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ ├── HubSpotSprocket.jsx
│ │ │ ├── Layout.jsx
│ │ │ ├── Link.jsx
│ │ │ ├── islands
│ │ │ │ ├── Button.jsx
│ │ │ │ └── TodoList.jsx
│ │ │ ├── modules
│ │ │ │ └── TodoList
│ │ │ │ │ ├── fields.jsx
│ │ │ │ │ └── index.jsx
│ │ │ └── partials
│ │ │ │ ├── Cars.jsx
│ │ │ │ ├── Footer.jsx
│ │ │ │ ├── Header.jsx
│ │ │ │ └── Home.jsx
│ │ ├── package.json
│ │ ├── styles
│ │ │ ├── cars.module.css
│ │ │ ├── footer.module.css
│ │ │ ├── header.module.css
│ │ │ ├── home.module.css
│ │ │ ├── layout.module.css
│ │ │ └── todo.module.css
│ │ ├── tests
│ │ │ └── components
│ │ │ │ ├── __snapshots__
│ │ │ │ └── partials.home.test.jsx.snap
│ │ │ │ └── partials.home.test.jsx
│ │ └── vitest.config.js
│ └── hsproject.json
├── hello-world-theme
│ ├── cars.hubl.html
│ ├── fields.json
│ ├── home.hubl.html
│ ├── layouts
│ │ └── base.hubl.html
│ ├── theme.json
│ └── todo.hubl.html
├── jsconfig.json
└── package.json
├── islands
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── islands-project
│ ├── .hsignore
│ ├── hsproject.json
│ └── islands-app
│ │ ├── .node-version
│ │ ├── Globals.d.ts
│ │ ├── cms-assets.json
│ │ ├── components
│ │ ├── ButtonCounter
│ │ │ ├── ButtonCounter.module.css
│ │ │ ├── ButtonCounter.tsx
│ │ │ └── index.tsx
│ │ ├── Clock.jsx
│ │ └── partials
│ │ │ ├── BasicIslandClock.jsx
│ │ │ ├── IslandsTester.jsx
│ │ │ └── IslandsTesterPlus.jsx
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── vitest.config.js
├── islands-theme
│ ├── fields.json
│ ├── islands.hubl.html
│ ├── layouts
│ │ └── base.hubl.html
│ └── theme.json
└── package.json
├── module-components
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── jsconfig.json
├── module-components-project
│ ├── .hsignore
│ ├── hsproject.json
│ └── module-components-app
│ │ ├── cms-assets.json
│ │ ├── components
│ │ └── modules
│ │ │ ├── IconExample
│ │ │ └── index.jsx
│ │ │ └── RichTextExample
│ │ │ └── index.jsx
│ │ └── package.json
└── package.json
├── routing
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── routing-project
│ ├── hsproject.json
│ └── routing-app
│ │ ├── Global.d.ts
│ │ ├── assets
│ │ ├── dynamic-slug-screenshot.png
│ │ └── pokeball.svg
│ │ ├── cms-assets.json
│ │ ├── components
│ │ ├── Header.tsx
│ │ ├── Home.tsx
│ │ ├── ListingCard.tsx
│ │ ├── Pokedex.tsx
│ │ ├── Pokemon.tsx
│ │ ├── ProfileCard.tsx
│ │ ├── islands
│ │ │ └── App.tsx
│ │ └── modules
│ │ │ └── Router
│ │ │ └── index.tsx
│ │ ├── constants.ts
│ │ ├── package.json
│ │ ├── styles
│ │ ├── card.module.css
│ │ ├── header.module.css
│ │ └── page.module.css
│ │ └── tsconfig.json
└── routing-theme
│ ├── fields.json
│ ├── layouts
│ └── base.hubl.html
│ ├── router-page.hubl.html
│ └── theme.json
├── serverless
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── cms-with-serverless-project
│ ├── .hsignore
│ ├── app
│ │ ├── app.functions
│ │ │ ├── package.json
│ │ │ ├── parrot-function.js
│ │ │ └── serverless.json
│ │ └── app.json
│ ├── cms-react
│ │ ├── cms-assets.json
│ │ ├── components
│ │ │ └── modules
│ │ │ │ └── MakeServerlessRequest
│ │ │ │ ├── MakeServerlessRequestIsland.jsx
│ │ │ │ └── index.jsx
│ │ └── package.json
│ └── hsproject.json
├── jsconfig.json
└── package.json
├── styling
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── package.json
├── styling-project
│ ├── .hsignore
│ ├── hsproject.json
│ └── styling-app
│ │ ├── .node-version
│ │ ├── assets
│ │ ├── biker-tailwind-ai.png
│ │ ├── dog-named-tailwind-ai.png
│ │ └── windy-trail-ai.png
│ │ ├── cms-assets.json
│ │ ├── components
│ │ ├── InteractiveStyledComponent.jsx
│ │ ├── InteractiveStyledJSXComponent.jsx
│ │ ├── StyledComponentsIsland.jsx
│ │ ├── StyledComponentsRegistry.jsx
│ │ ├── StyledJSXIsland.jsx
│ │ ├── StyledJSXRegistry.jsx
│ │ ├── islands
│ │ │ └── ToggleSwitch.jsx
│ │ └── partials
│ │ │ ├── CSSModulesPartial.jsx
│ │ │ ├── CSSPropertiesPartial.jsx
│ │ │ ├── InlineStylesPartial.jsx
│ │ │ ├── StyledComponentsPartial.jsx
│ │ │ ├── StyledJSXPartial.jsx
│ │ │ └── TailwindPartial.jsx
│ │ ├── jsconfig.json
│ │ ├── package.json
│ │ ├── postcss.config.mjs
│ │ ├── styles
│ │ ├── CSSModulesPartial.module.css
│ │ ├── CSSPropertiesPartial.css
│ │ └── tailwind.css
│ │ ├── tailwind.config.js
│ │ └── vitest.config.js
└── styling-theme
│ ├── fields.json
│ ├── layouts
│ └── base.hubl.html
│ ├── styling.hubl.html
│ ├── tailwind.hubl.html
│ └── theme.json
└── todo-mvc
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE
├── jsconfig.json
├── package.json
├── todo-mvc-project
├── .hsignore
├── hsproject.json
└── todo-mvc-app
│ ├── .node-version
│ ├── cms-assets.json
│ ├── components
│ ├── modules
│ │ └── TodoMVCModule
│ │ │ ├── fields.jsx
│ │ │ └── index.jsx
│ └── todomvc-separate-islands
│ │ ├── components
│ │ ├── app
│ │ │ └── app.jsx
│ │ ├── copy-right
│ │ │ └── copy-right.jsx
│ │ ├── footer
│ │ │ └── footer.jsx
│ │ ├── header
│ │ │ ├── TodoInput.jsx
│ │ │ └── header.jsx
│ │ ├── hooks
│ │ │ └── useFilterFromURL.js
│ │ ├── item
│ │ │ └── item.jsx
│ │ └── list
│ │ │ └── list.jsx
│ │ ├── constants
│ │ ├── action-type.js
│ │ └── filter.js
│ │ ├── index.jsx
│ │ └── store
│ │ ├── actions
│ │ └── todo.js
│ │ ├── reducers
│ │ └── todo.js
│ │ └── selectors
│ │ └── todo.js
│ ├── package.json
│ └── vitest.config.js
└── todo-mvc-theme
├── fields.json
├── layouts
└── base.hubl.html
├── theme.json
└── todo-mvc.hubl.html
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: CMS React bug report
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 |
10 | ### Description
11 |
12 |
13 | ### Steps to Reproduce
14 |
20 |
21 | ### Expected Behavior
22 |
23 |
24 | ### Actual Behavior
25 |
26 |
27 | ### Environment
28 | - OS:
29 | - Node version:
30 | - @hubspot/cli version:
31 | - @hubspot/cms-dev-server version:
32 | - @hubspot/cms-components version:
33 |
34 | ### Logs or Error Messages
35 |
36 |
37 | ### Screenshots/GIFs
38 |
39 |
40 | ### Additional Context
41 |
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy docs to GH Pages
2 |
3 | on:
4 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're
5 | # using the `master` branch as the default branch.
6 | push:
7 | branches: [main]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: pages
22 | cancel-in-progress: false
23 |
24 | defaults:
25 | run:
26 | working-directory: docs
27 |
28 | jobs:
29 | # Build job
30 | build:
31 | runs-on: ubuntu-latest
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@v4
35 | with:
36 | fetch-depth: 0 # Not needed if lastUpdated is not enabled
37 | - name: Setup Node
38 | uses: actions/setup-node@v4
39 | with:
40 | node-version: 18
41 | cache: yarn # or pnpm / yarn
42 | cache-dependency-path: 'docs/yarn.lock'
43 | - name: Setup Pages
44 | uses: actions/configure-pages@v4
45 | - name: Install dependencies
46 | run: yarn # or pnpm install / yarn install / bun install
47 | - name: Build with VitePress
48 | run: |
49 | yarn build
50 | touch .vitepress/dist/.nojekyll
51 | - name: Upload artifact
52 | uses: actions/upload-pages-artifact@v3
53 | with:
54 | path: docs/.vitepress/dist
55 |
56 | # Deployment job
57 | deploy:
58 | environment:
59 | name: github-pages
60 | url: ${{ steps.deployment.outputs.page_url }}
61 | needs: build
62 | runs-on: ubuntu-latest
63 | name: Deploy
64 | steps:
65 | - name: Deploy to GitHub Pages
66 | id: deployment
67 | uses: actions/deploy-pages@v4
68 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-test.yml:
--------------------------------------------------------------------------------
1 | name: Project build and deploy test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | deploy-test:
10 | strategy:
11 | matrix:
12 | os: [windows-latest, ubuntu-latest]
13 | node: [18]
14 |
15 | runs-on: ${{ matrix.os }}
16 |
17 | defaults:
18 | run:
19 | working-directory: examples/hello-world/hello-world-project
20 | shell: bash
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - uses: actions/setup-node@v3
25 | with:
26 | node-version: ${{ matrix.node }}
27 |
28 | - name: Rename project
29 | run: |
30 | sed -i 's/hello-world-project/hello-world-project-os-${{ matrix.os }}-node-${{ matrix.node }}/g' hsproject.json
31 | echo ""
32 | echo "After rename"
33 | cat hsproject.json
34 |
35 | - name: Build and deploy
36 | env:
37 | HUBSPOT_PORTAL_ID: ${{ secrets.hubspot_portal_id }}
38 | HUBSPOT_PERSONAL_ACCESS_KEY: ${{ secrets.hubspot_personal_access_key }}
39 | run: |
40 | npx --yes --package=@hubspot/cli@latest --call='hs project upload --use-env'
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/smoke-test.yml:
--------------------------------------------------------------------------------
1 | name: CMS dev server test
2 |
3 | on: push
4 |
5 | jobs:
6 | dev-server-smoke-test:
7 | strategy:
8 | matrix:
9 | os: [windows-latest, ubuntu-latest]
10 | node: [16, 18]
11 |
12 | runs-on: ${{ matrix.os }}
13 |
14 | defaults:
15 | run:
16 | working-directory: examples/hello-world
17 | shell: bash
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ matrix.node }}
24 |
25 | - name: NPM install
26 | run: |
27 | npm install
28 |
29 | - name: Run dev server
30 | run: |
31 | npm run start &
32 |
33 | - name: Basic dev server home test
34 | run: |
35 | curl --no-progress-meter http://localhost:3000/ --output home.txt
36 | ls -lh
37 | wc home.txt
38 | echo "Grepping home.txt"
39 | grep -q '/partial/Cars' home.txt
40 | echo "-----"
41 | cat home.txt
42 |
43 | - name: Basic dev server partial test
44 | run: |
45 | curl --no-progress-meter http://localhost:3000/partial/Cars --output Cars.txt
46 | echo "Grepping Cars.txt"
47 | grep -q 'Volkswagen' Cars.txt
48 | echo "-----"
49 | cat Cars.txt
50 |
51 |
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | hubspot.config.yml
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HubSpot CMS React
2 |
3 | A collection of CMS React examples for building on the HubSpot CMS.
4 |
5 | ## Table of Contents
6 |
7 | - [Overview](#overview)
8 | - [Documentation](#documentation)
9 | - [Examples](#examples)
10 | - [Contributing](#contributing)
11 | - [Support](#support)
12 |
13 | ---
14 |
15 | ## Overview
16 |
17 | This repo demonstrates how to build React modules and "islands" for the HubSpot CMS. You'll find step-by-step examples, best practices, and customizable boilerplates to get you up and running quickly.
18 |
19 | ## Documentation
20 |
21 | - Official HubSpot CMS React docs:
22 | https://developers.hubspot.com/docs/guides/cms/react/overview
23 |
24 | ## Examples
25 | Each folder under `examples/` is a standalone example focused on a specific use case or feature. If you are just getting started with CMS React, the following examples are the best places to start:
26 |
27 | - **[getting-started-project-theme](examples/getting-started-project-theme)**
28 | Building a CMS theme with React and Projects
29 | - **[getting-started](examples/getting-started)**
30 | Building a standalone Project with React modules that can be used across CMS themes
31 |
32 | If you want to bootstrap a new Project with CMS React you can run `npx @hubspot/create-cms-theme@latest` which will create a Project starting point for you to build upon. [Learn More](https://www.npmjs.com/package/@hubspot/create-cms-theme)
33 |
34 | ---
35 |
36 | ## Contributing
37 |
38 | We welcome bug reports and PRs to update or add new examples:
39 |
40 | 1. Fork this repo
41 | 2. Create a feature branch (`git checkout -b my-new-example`)
42 | 3. Commit your changes (`git commit -m "Add amazing example"`)
43 | 4. Open a PR
44 |
45 | ---
46 |
47 | ## Support
48 |
49 | Chat with the community on Slack:
50 | https://developers.hubspot.com/docs/getting-started/slack/developer-slack
51 |
52 | ---
53 |
--------------------------------------------------------------------------------
/docs/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitepress';
2 |
3 | // https://vitepress.dev/reference/site-config
4 | export default defineConfig({
5 | title: 'HubSpot - CMS React',
6 | description: 'Archived documentation site for HubSpot CMS React',
7 | base: '/cms-react/',
8 | themeConfig: {
9 | // https://vitepress.dev/reference/default-theme-config
10 | nav: [
11 | { text: 'Official Docs', link: 'https://developers.hubspot.com/docs/guides/cms/react/overview' },
12 | { text: 'Examples', link: 'https://github.com/HubSpot/cms-react/tree/main/examples' },
13 | { text: 'Release Log', link: 'release-log' },
14 | ],
15 | },
16 | ignoreDeadLinks: true,
17 | });
18 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # This site has been archived
2 | Please refer to the [official documentation](https://developers.hubspot.com/docs/guides/cms/react/overview) for the latest CMS React information.
3 |
4 | ## Quicklinks
5 | - [Getting started](https://developers.hubspot.com/docs/guides/cms/quickstart/react-plus-hubl-quickstart)
6 | - [Field types](https://developers.hubspot.com/docs/reference/cms/fields/module-theme-fields)
7 |
8 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vitepress dev",
7 | "build": "vitepress build",
8 | "preview": "vitepress preview",
9 | "serve": "vitepress serve"
10 | },
11 | "dependencies": {},
12 | "devDependencies": {
13 | "vitepress": "1.0.0-rc.11"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 |
--------------------------------------------------------------------------------
/examples/contact-profile/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/hello-world-app/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/contact-profile/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/contact-profile/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Contact Profile - React",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Column.jsx:
--------------------------------------------------------------------------------
1 | import styles from '../styles/theme.module.css';
2 |
3 | const Column = ({ className, children, style = {}, width = 12 }) => {
4 | return (
5 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | export default Column;
15 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Company.jsx:
--------------------------------------------------------------------------------
1 | import Row from './Row';
2 | import Column from './Column';
3 |
4 | const ProfileName = ({ name, description }) => {
5 | return (
6 |
7 |
8 | Primary Company:
9 |
10 |
11 |
12 | {name}
13 |
14 |
15 |
16 |
17 | {description}
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default ProfileName;
25 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/FormInput.jsx:
--------------------------------------------------------------------------------
1 | const FormInput = ({
2 | helpText,
3 | id = 'no-id',
4 | label = 'Missing Label',
5 | type = 'text',
6 | defaultValue,
7 | }) => {
8 | return (
9 |
10 |
11 | {label} {helpText}
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default FormInput;
19 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Grid.jsx:
--------------------------------------------------------------------------------
1 | import styles from '../styles/theme.module.css';
2 |
3 | const Grid = ({ className, children, style = {} }) => {
4 | return (
5 |
11 | {children}
12 |
13 | );
14 | };
15 |
16 | export default Grid;
17 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import layoutStyles from '../styles/layout.module.css';
2 |
3 | function Layout({ children }) {
4 | return {children}
;
5 | }
6 |
7 | export default Layout;
8 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Link.jsx:
--------------------------------------------------------------------------------
1 | function Link(props) {
2 | return {props.title} ;
3 | }
4 |
5 | export default Link;
6 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/ProfileImage.jsx:
--------------------------------------------------------------------------------
1 | import { Pencil } from './SVGs';
2 |
3 | const AVATAR_BG_COLOR = 'rgb(142 124 195)';
4 |
5 | const ProfileImage = ({ firstName = 'John Doe' }) => {
6 | return (
7 |
20 | {firstName && firstName.slice(0, 1)}
21 |
46 |
47 | );
48 | };
49 |
50 | export default ProfileImage;
51 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/ProfileName.jsx:
--------------------------------------------------------------------------------
1 | import Row from './Row';
2 | import Column from './Column';
3 |
4 | import styles from '../styles/theme.module.css';
5 |
6 | const ProfileName = ({ firstName, lastName, city, country, style = {} }) => {
7 | return (
8 |
9 |
10 |
11 |
12 | {firstName} {lastName}
13 |
14 |
15 |
16 |
17 |
18 |
19 | {city} · {country}
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default ProfileName;
28 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/Row.jsx:
--------------------------------------------------------------------------------
1 | import styles from '../styles/theme.module.css';
2 |
3 | const Row = ({ children, className, style = {} }) => {
4 | return (
5 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | export default Row;
15 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/SVGs.jsx:
--------------------------------------------------------------------------------
1 | export const Pencil = ({ style = {} }) => (
2 |
9 |
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/modules/ContactProfile/fields.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ModuleFields, ColorField } from '@hubspot/cms-components/fields';
3 |
4 | export const fields = (
5 |
6 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/components/modules/ContactWithAssociations/fields.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ModuleFields, ColorField } from '@hubspot/cms-components/fields';
3 |
4 | export const fields = (
5 |
6 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contact-profile-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0"
9 | },
10 | "devDependencies": {
11 | "@hubspot/cms-dev-server": "latest",
12 | "@testing-library/react": "^13.4.0",
13 | "@vitejs/plugin-react": "^2.1.0",
14 | "jsdom": "^20.0.1",
15 | "vitest": "^0.24.3"
16 | },
17 | "scripts": {
18 | "start": "hs-cms-dev-server .",
19 | "test": "vitest"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/styles/layout.module.css:
--------------------------------------------------------------------------------
1 | :global(html) {
2 | height: 100%;
3 | }
4 |
5 | :global(body) {
6 | font-family: Arial, Helvetica, sans-serif;
7 | height: 100%;
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | :global(.body-wrapper) {
13 | height: 100%;
14 | display: flex;
15 | flex-direction: column;
16 | }
17 |
18 | .layout {
19 | flex: 1;
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | flex-direction: column;
24 | }
25 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/contact-profile-app/styles/theme.module.css:
--------------------------------------------------------------------------------
1 | .rowFluidWrapper {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
6 | .rowFluid {
7 | display: flex;
8 | }
9 |
10 | .col {
11 | flex: 1;
12 | }
13 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contact-profile-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-theme/contact-profile.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module "contact_profile"
11 | path="@projects/contact-profile-project/contact-profile-app/components/modules/ContactProfile",
12 | firstName="{{contact.firstname}}",
13 | lastName="{{contact.lastname}}",
14 | email="{{contact.email}}",
15 | foo="bar" %}
16 |
17 | {% endblock body %}
18 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-theme/contact-with-associations.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module "contact_profile_with_associations"
11 | path="@projects/contact-profile-project/contact-profile-app/components/modules/ContactWithAssociations" %}
12 |
13 | {% endblock body %}
14 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {# 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 #}
23 |
24 | {% block body %}
25 | {% endblock body %}
26 |
27 | {% block footer %}
28 | {% endblock footer %}
29 |
30 |
31 | {# 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 #}
32 | {{ standard_footer_includes }}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/contact-profile/contact-profile-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Contact Profile",
3 | "preview_path": "./contact-profile.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/contact-profile/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/contact-profile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-contact-profile",
3 | "description": "JS Building Blocks Contact Profile",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd contact-profile-project/contact-profile-app && yarpm start",
16 | "postinstall": "cd contact-profile-project/contact-profile-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch contact-profile-theme contact-profile-theme",
20 | "upload:hubl": "hs upload contact-profile-theme contact-profile-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "dependencies": {},
24 | "engines": {
25 | "node": ">=16.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/data-fetching/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | settings: {
18 | react: {
19 | version: '18.1',
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/examples/data-fetching/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/data-fetching/.node-version:
--------------------------------------------------------------------------------
1 | 20.11.0
2 |
--------------------------------------------------------------------------------
/examples/data-fetching/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/data-fetching/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
13 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/Globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React - Data fetching",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/components/PokeCard.tsx:
--------------------------------------------------------------------------------
1 | import pokeCardStyles from '../styles/pokecard.module.css';
2 | import { getTypeColor, PokemonTypes } from '../utils/index.js';
3 |
4 | type PokemonData = {
5 | pokemonName: string;
6 | height: number;
7 | weight: number;
8 | profileImage: string;
9 | pokemonType: PokemonTypes;
10 | };
11 |
12 | type PokeCardProps = {
13 | pokemonData: PokemonData;
14 | };
15 |
16 | export default function PokeCard({ pokemonData }: PokeCardProps) {
17 | const { pokemonName, height, weight, profileImage, pokemonType } =
18 | pokemonData;
19 | const typeColor = getTypeColor(pokemonType);
20 |
21 | return (
22 |
23 |
27 |
28 |
29 |
30 |
31 |
{pokemonName}
32 |
33 |
34 |
Height
35 |
{height}m
36 |
37 |
38 |
Weight
39 |
{weight}kg
40 |
41 |
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/components/modules/Fetcher/fields.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BooleanField,
3 | ChoiceField,
4 | ModuleFields,
5 | TextField,
6 | } from '@hubspot/cms-components/fields';
7 |
8 | export const fields = (
9 |
10 |
29 |
39 |
44 |
61 |
62 | );
63 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "data-fetching-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@apollo/client": "^3.9.7",
7 | "@hubspot/cms-components": "latest",
8 | "axios": "^1.6.8",
9 | "graphql": "^16.8.1",
10 | "graphql-request": "^6.1.0",
11 | "node-fetch": "^3.3.2",
12 | "node-fetch-cache": "^4.1.0",
13 | "prop-types": "^15.8.1",
14 | "react": "^18.1.0"
15 | },
16 | "devDependencies": {
17 | "@hubspot/cms-dev-server": "latest",
18 | "@testing-library/react": "^13.4.0",
19 | "@vitejs/plugin-react": "^2.1.0",
20 | "jsdom": "^20.0.1",
21 | "vitest": "^0.24.3"
22 | },
23 | "scripts": {
24 | "start": "hs-cms-dev-server .",
25 | "test": "vitest"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/styles/component.module.css:
--------------------------------------------------------------------------------
1 | .summary h2 code {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/styles/pokecard.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | gap: 10px;
4 | }
5 |
6 | .card {
7 | display: flex;
8 | border-radius: 5px;
9 | background-color: #303030;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 20px 20px 25px 20px;
14 | margin: 10px;
15 | min-height: 300px;
16 | min-width: 225px;
17 | gap: 20px;
18 | }
19 |
20 | .profile {
21 | display: flex;
22 | justify-content: center;
23 | align-items: center;
24 | }
25 |
26 | .profile .highlight {
27 | width: 175px;
28 | height: 175px;
29 | background: transparent;
30 | border-radius: 50%;
31 | position: absolute;
32 | box-shadow: 0px 0px 0px 0px #fff, 0px 0px 50px inset rgba(255, 255,255, 0.5);
33 | z-index: 0;
34 | }
35 |
36 | .card img {
37 | width: 100%;
38 | max-width: 100px;
39 | align-self: center;
40 | z-index: 1;
41 | height: 175px;
42 | }
43 |
44 | .attributes {
45 | display: flex;
46 | justify-content: space-around;
47 | width: 100%;
48 | margin-top: 10px;
49 | }
50 |
51 | .attributes h4 {
52 | margin-top: 0;
53 | margin-bottom: 5px;
54 | color: white;
55 | }
56 |
57 | .attributes p {
58 | margin: 0;
59 | color: white;
60 | }
61 |
62 | .stack {
63 | display: flex;
64 | justify-content: center;
65 | align-items: center;
66 | flex-direction: column;
67 | }
68 |
69 | .card h2 {
70 | margin: 0px;
71 | color: white;
72 | }
73 |
74 | .card span {
75 | padding: 2.5px 5px;
76 | background-color: #f0f0f0;
77 | }
78 |
79 | ul {
80 | margin-top: 0px;
81 | }
82 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/data-fetching-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "Node16",
5 | "moduleResolution": "node16",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "jsx": "react-jsx",
11 | "plugins": [
12 | {
13 | "name": "typescript-plugin-css-modules"
14 | }
15 | ]
16 | },
17 | "$schema": "https://json.schemastore.org/tsconfig",
18 | "display": "Recommended"
19 | }
20 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "data-fetching-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-theme/data-fetching.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 | {% block body %}
8 | {% module 'fetcher'
9 | path="@projects/data-fetching-project/data-fetching-app/components/modules/Fetcher"
10 | %}
11 | {% endblock body %}
12 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-theme/fields.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {% block body %}
23 | {% endblock body %}
24 |
25 | {% block footer %}
26 | {% endblock footer %}
27 |
28 |
29 | {# 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 #}
30 | {{ standard_footer_includes }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/data-fetching/data-fetching-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Data fetching theme"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/data-fetching/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-react-data-fetching",
3 | "description": "CMS React Data Fetching",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd data-fetching-project/data-fetching-app && yarpm start --",
16 | "postinstall": "cd data-fetching-project/data-fetching-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --write",
19 | "watch:hubl": "hs watch data-fetching-theme data-fetching-theme",
20 | "upload:hubl": "hs upload data-fetching-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "engines": {
24 | "node": ">=20.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | settings: {
18 | react: {
19 | version: '18.1',
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/.node-version:
--------------------------------------------------------------------------------
1 | 20.11.0
2 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-project-theme",
3 | "srcDir": "src",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-project-theme-example",
3 | "description": "Getting started CMS Themes on Projects",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd src/getting-started-theme && yarpm start",
16 | "postinstall": "cd src/getting-started-theme && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "deploy": "hs project upload"
20 | },
21 | "engines": {
22 | "node": ">=16.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/Globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/assets/clear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/assets/cloudy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/components/WeatherCards.tsx:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import weatherStyles from '../styles/weather.module.css';
3 | import { getWeatherIcon } from '../utils.ts';
4 | import { ForecastData } from '../constants.ts';
5 |
6 | interface WeatherProps {
7 | city: string;
8 | forecast: ForecastData[];
9 | }
10 | interface CurrentWeatherCardProps {
11 | weatherData: WeatherProps;
12 | }
13 |
14 | export function CurrentWeatherCard({ weatherData }: CurrentWeatherCardProps) {
15 | const { forecast, city } = weatherData;
16 | const currentDay = forecast[0];
17 |
18 | return (
19 |
20 |
21 |
25 |
26 | {currentDay.apparent_temperature_max}°
27 | F
28 |
29 |
30 |
{city}
31 |
32 | );
33 | }
34 |
35 | interface UpcomingWeatherCardProps {
36 | weatherData: WeatherProps;
37 | }
38 |
39 | export function UpcomingWeatherCard({ weatherData }: UpcomingWeatherCardProps) {
40 | const { city, forecast } = weatherData;
41 |
42 | return (
43 | <>
44 | {forecast?.map((weather, index: number) => {
45 | if (index === 0) return null;
46 |
47 | return (
48 |
49 |
{dayjs(weather.time).format('dddd')}
50 |
54 |
55 | {weather.apparent_temperature_max}°
56 | F
57 |
58 |
59 | );
60 | })}
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/components/islands/WeatherForecast.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import weatherStyles from '../../styles/weather.module.css';
3 | import { getWeatherForecast } from '../../utils.ts';
4 | import { WeatherForecast as WeatherForecastType } from '../../constants.ts';
5 | import { CurrentWeatherCard, UpcomingWeatherCard } from '../WeatherCards.tsx';
6 |
7 | interface WeatherForecastProps {
8 | headline: string;
9 | }
10 |
11 | export default function WeatherForecast({ headline }: WeatherForecastProps) {
12 | const [city, setCity] = useState('');
13 | const [weatherData, setWeatherData] = useState();
14 |
15 | const handleFetchWeather = () => {
16 | getWeatherForecast(city).then((data) => {
17 | setWeatherData(data);
18 | });
19 | };
20 |
21 | const isFetching: boolean = !weatherData;
22 | const hasError: boolean = !isFetching && !!weatherData.error;
23 | const hasWeatherData: boolean =
24 | !isFetching && !hasError && !!weatherData.forecast;
25 | const missingData = !isFetching && !hasWeatherData && !hasError;
26 |
27 | function WeatherForecast({ weatherData }) {
28 | return (
29 | <>
30 |
31 |
32 |
33 |
34 |
35 |
36 | >
37 | );
38 | }
39 |
40 | return (
41 |
42 |
{headline}
43 |
44 | setCity(event.target.value)}
48 | />
49 | Update Forecast
50 |
51 |
52 | {isFetching &&
Search for a city to see the weather forecast }
53 | {hasError && Error occurred when fetching weather forecast }
54 | {hasWeatherData && }
55 | {missingData && (
56 | No results found for "{city}", please search another location
57 | )}
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/components/modules/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import { ModuleFields, TextField } from '@hubspot/cms-components/fields';
2 | import footerStyles from '../../../styles/footer.module.css';
3 |
4 | export function Component({ fieldValues }: any) {
5 | return (
6 |
7 | {fieldValues.footerText}
8 |
9 | );
10 | }
11 |
12 | export const fields = (
13 |
14 |
15 |
16 | );
17 |
18 | export const meta = {
19 | label: 'Footer Module',
20 | };
21 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/components/modules/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu, logInfo } from '@hubspot/cms-components';
3 | import {
4 | ImageField,
5 | MenuField,
6 | ModuleFields,
7 | } from '@hubspot/cms-components/fields';
8 | import logo from '../../../assets/sprocket.svg';
9 | import headerStyles from '../../../styles/header.module.css';
10 |
11 | export function Component({ fieldValues }: any) {
12 | const { src, alt, width, height } = fieldValues.logo;
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | const DEFAULT_MENU_ID = '
28 |
34 |
35 |
36 | );
37 |
38 | export const meta = {
39 | label: 'Header Module',
40 | };
41 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/components/modules/Weather/index.tsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import WeatherForecast from '../../islands/WeatherForecast.tsx?island';
3 | import { ModuleFields, TextField } from '@hubspot/cms-components/fields';
4 |
5 | export function Component({ fieldValues }: any) {
6 | const { headline } = fieldValues;
7 | return ;
8 | }
9 |
10 | export const fields = (
11 |
12 |
17 |
18 | );
19 |
20 | export const meta = {
21 | label: 'Weather Module',
22 | };
23 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/constants.ts:
--------------------------------------------------------------------------------
1 | import CLEAR from './assets/clear.svg';
2 | import RAIN from './assets/rain.svg';
3 | import SNOW from './assets/snow.svg';
4 | import THUNDERSTORM from './assets/thunderstorm.svg';
5 | import FOG from './assets/fog.svg';
6 | import CLOUDY from './assets/cloudy.svg';
7 |
8 | export const ICON_MAP = {
9 | CLEAR,
10 | RAIN,
11 | SNOW,
12 | THUNDERSTORM,
13 | FOG,
14 | CLOUDY,
15 | };
16 |
17 | export const FORECAST_BASE_URL = 'https://api.open-meteo.com/v1/forecast'; // https://open-meteo.com/en/docs
18 | export const LAT_LNG_BASE_URL =
19 | 'https://geocoding-api.open-meteo.com/v1/search';
20 |
21 | export interface Forecast {
22 | time: string[];
23 | apparent_temperature_max: number[];
24 | weather_code: number[];
25 | }
26 |
27 | export interface ForecastData {
28 | time: string;
29 | apparent_temperature_max: number;
30 | weather_code: number;
31 | }
32 |
33 | interface LocationResult {
34 | latitude: number;
35 | longitude: number;
36 | }
37 |
38 | export interface LocationResponse {
39 | results: LocationResult[];
40 | }
41 |
42 | export interface ForecastResponse {
43 | daily: Forecast;
44 | }
45 |
46 | export interface WeatherForecast {
47 | city: string;
48 | forecast: ForecastData[];
49 | error?: string;
50 | }
51 |
52 | interface DailyUnits {
53 | time: string;
54 | apparent_temperature_max: string;
55 | weather_code: string;
56 | }
57 |
58 | export interface ApiResponse {
59 | latitude: number;
60 | longitude: number;
61 | generationtime_ms: number;
62 | utc_offset_seconds: number;
63 | timezone: string;
64 | timezone_abbreviation: string;
65 | elevation: number;
66 | daily_units: DailyUnits;
67 | daily: Forecast;
68 | }
69 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/fields.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-example-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "dayjs": "^1.11.11",
8 | "prop-types": "^15.8.1",
9 | "react": "^18.1.0"
10 | },
11 | "devDependencies": {
12 | "@hubspot/cms-dev-server": "latest",
13 | "@testing-library/react": "^13.4.0",
14 | "@vitejs/plugin-react": "^2.1.0",
15 | "jsdom": "^20.0.1",
16 | "vitest": "^0.24.3"
17 | },
18 | "scripts": {
19 | "start": "hs-cms-dev-server . --ssl",
20 | "test": "vitest"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/styles/footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | text-align: center;
3 | font-weight: bold;
4 | font-size: 2em;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/styles/header.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | width: 100%;
3 | background-color: transparent;
4 |
5 | & nav {
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | flex-direction: row;
10 | padding: 15px 25px;
11 |
12 | & ul {
13 | gap: 20px;
14 | list-style: none;
15 | margin: 0;
16 | padding: 0;
17 | display: flex;
18 |
19 | & li {
20 | & a {
21 | text-decoration: none;
22 | color: #ff7a59;
23 | &:hover {
24 | color: #2d3e50;
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/templates/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% module 'main header' path="../../components/modules/Header" %}
21 | {% endblock header %}
22 |
23 | {# 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 #}
24 |
25 | {% block body %}
26 | {% endblock body %}
27 |
28 | {% block footer %}
29 | {% module 'footer' path="../../components/modules/Footer" %}
30 | {% endblock footer %}
31 |
32 |
33 | {# 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 #}
34 | {{ standard_footer_includes }}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/templates/weather.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module
11 | "weather"
12 | path="../components/modules/Weather",
13 | %}
14 |
15 | {% endblock body %}
16 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React - Getting Started Project Theme"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/getting-started-project-theme/src/getting-started-theme/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "allowImportingTsExtensions": true,
10 | "noEmit": true,
11 | "isolatedModules": true,
12 | "jsx": "react-jsx",
13 | "skipLibCheck": true,
14 | "types": ["vite/client", "vitest/globals"]
15 | },
16 | "include": ["components/**/*"],
17 | "exclude": ["node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/getting-started/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | settings: {
18 | react: {
19 | version: '18.1',
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/examples/getting-started/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/getting-started/.node-version:
--------------------------------------------------------------------------------
1 | 20.11.0
2 |
--------------------------------------------------------------------------------
/examples/getting-started/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/Globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/assets/clear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/assets/cloudy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Getting started",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/components/WeatherCards.tsx:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import weatherStyles from '../styles/weather.module.css';
3 | import { getWeatherIcon } from '../utils.ts';
4 | import { ForecastData } from '../constants.ts';
5 |
6 | interface WeatherProps {
7 | city: string;
8 | forecast: ForecastData[];
9 | }
10 | interface CurrentWeatherCardProps {
11 | weatherData: WeatherProps;
12 | }
13 |
14 | export function CurrentWeatherCard({ weatherData }: CurrentWeatherCardProps) {
15 | const { forecast, city } = weatherData;
16 | const currentDay = forecast[0];
17 |
18 | return (
19 |
20 |
21 |
25 |
26 | {currentDay.apparent_temperature_max}°
27 | F
28 |
29 |
30 |
{city}
31 |
32 | );
33 | }
34 |
35 | interface UpcomingWeatherCardProps {
36 | weatherData: WeatherProps;
37 | }
38 |
39 | export function UpcomingWeatherCard({ weatherData }: UpcomingWeatherCardProps) {
40 | const { city, forecast } = weatherData;
41 |
42 | return (
43 | <>
44 | {forecast?.map((weather, index: number) => {
45 | if (index === 0) return null;
46 |
47 | return (
48 |
49 |
{dayjs(weather.time).format('dddd')}
50 |
54 |
55 | {weather.apparent_temperature_max}°
56 | F
57 |
58 |
59 | );
60 | })}
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/components/modules/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import { ModuleFields, TextField } from '@hubspot/cms-components/fields';
2 | import footerStyles from '../../../styles/footer.module.css';
3 |
4 | export function Component({ fieldValues }: any) {
5 | return (
6 |
7 | {fieldValues.footerText}
8 |
9 | );
10 | }
11 |
12 | export const fields = (
13 |
14 |
15 |
16 | );
17 |
18 | export const meta = {
19 | label: 'Footer Module',
20 | };
21 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/components/modules/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu, logInfo } from '@hubspot/cms-components';
3 | import {
4 | ImageField,
5 | MenuField,
6 | ModuleFields,
7 | } from '@hubspot/cms-components/fields';
8 | import logo from '../../../assets/sprocket.svg';
9 | import headerStyles from '../../../styles/header.module.css';
10 |
11 | export function Component({ fieldValues }: any) {
12 | const { src, alt, width, height } = fieldValues.logo;
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | const DEFAULT_MENU_ID = '
28 |
34 |
35 |
36 | );
37 |
38 | export const meta = {
39 | label: 'Header Module',
40 | };
41 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/components/modules/Weather/index.tsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import WeatherForecast from '../../islands/WeatherForecast.tsx?island';
3 | import { ModuleFields, TextField } from '@hubspot/cms-components/fields';
4 |
5 | export function Component({ fieldValues }: any) {
6 | const { headline } = fieldValues;
7 | return ;
8 | }
9 |
10 | export const fields = (
11 |
12 |
17 |
18 | );
19 |
20 | export const meta = {
21 | label: 'Weather Module',
22 | };
23 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/constants.ts:
--------------------------------------------------------------------------------
1 | import CLEAR from './assets/clear.svg';
2 | import RAIN from './assets/rain.svg';
3 | import SNOW from './assets/snow.svg';
4 | import THUNDERSTORM from './assets/thunderstorm.svg';
5 | import FOG from './assets/fog.svg';
6 | import CLOUDY from './assets/cloudy.svg';
7 |
8 | export const ICON_MAP = {
9 | CLEAR,
10 | RAIN,
11 | SNOW,
12 | THUNDERSTORM,
13 | FOG,
14 | CLOUDY,
15 | };
16 |
17 | export const FORECAST_BASE_URL = 'https://api.open-meteo.com/v1/forecast'; // https://open-meteo.com/en/docs
18 | export const LAT_LNG_BASE_URL =
19 | 'https://geocoding-api.open-meteo.com/v1/search';
20 |
21 | export interface Forecast {
22 | time: string[];
23 | apparent_temperature_max: number[];
24 | weather_code: number[];
25 | }
26 |
27 | export interface ForecastData {
28 | time: string;
29 | apparent_temperature_max: number;
30 | weather_code: number;
31 | }
32 |
33 | interface LocationResult {
34 | latitude: number;
35 | longitude: number;
36 | }
37 |
38 | export interface LocationResponse {
39 | results: LocationResult[];
40 | }
41 |
42 | export interface ForecastResponse {
43 | daily: Forecast;
44 | }
45 |
46 | export interface WeatherForecast {
47 | city: string;
48 | forecast: ForecastData[];
49 | error?: string;
50 | }
51 |
52 | interface DailyUnits {
53 | time: string;
54 | apparent_temperature_max: string;
55 | weather_code: string;
56 | }
57 |
58 | export interface ApiResponse {
59 | latitude: number;
60 | longitude: number;
61 | generationtime_ms: number;
62 | utc_offset_seconds: number;
63 | timezone: string;
64 | timezone_abbreviation: string;
65 | elevation: number;
66 | daily_units: DailyUnits;
67 | daily: Forecast;
68 | }
69 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-example-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "dayjs": "^1.11.11",
8 | "prop-types": "^15.8.1",
9 | "react": "^18.1.0"
10 | },
11 | "devDependencies": {
12 | "@hubspot/cms-dev-server": "latest",
13 | "@testing-library/react": "^13.4.0",
14 | "@vitejs/plugin-react": "^2.1.0",
15 | "jsdom": "^20.0.1",
16 | "vitest": "^0.24.3"
17 | },
18 | "scripts": {
19 | "start": "hs-cms-dev-server .",
20 | "test": "vitest"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/styles/footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | text-align: center;
3 | font-weight: bold;
4 | font-size: 2em;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/styles/header.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | width: 100%;
3 | background-color: transparent;
4 |
5 | & nav {
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | flex-direction: row;
10 | padding: 15px 25px;
11 |
12 | & ul {
13 | gap: 20px;
14 | list-style: none;
15 | margin: 0;
16 | padding: 0;
17 | display: flex;
18 |
19 | & li {
20 | & a {
21 | text-decoration: none;
22 | color: #ff7a59;
23 | &:hover {
24 | color: #2d3e50;
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/getting-started-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "allowImportingTsExtensions": true,
10 | "noEmit": true,
11 | "isolatedModules": true,
12 | "jsx": "react-jsx",
13 | "skipLibCheck": true,
14 | "types": ["vite/client", "vitest/globals"]
15 | },
16 | "include": ["components/**/*"],
17 | "exclude": ["node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-theme/fields.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% module 'main header' path="@projects/getting-started-project/getting-started-app/components/modules/Header" %}
21 | {% endblock header %}
22 |
23 | {# 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 #}
24 |
25 | {% block body %}
26 | {% endblock body %}
27 |
28 | {% block footer %}
29 | {% module 'footer' path="@projects/getting-started-project/getting-started-app/components/modules/Footer" %}
30 | {% endblock footer %}
31 |
32 |
33 | {# 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 #}
34 | {{ standard_footer_includes }}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React - Getting Started Theme"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/getting-started/getting-started-theme/weather.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module
11 | "weather"
12 | path="@projects/getting-started-project/getting-started-app/components/modules/Weather",
13 | %}
14 |
15 | {% endblock body %}
16 |
--------------------------------------------------------------------------------
/examples/getting-started/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-example",
3 | "description": "Getting started with CMS-React",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd getting-started-project/getting-started-app && yarpm start",
16 | "postinstall": "cd getting-started-project/getting-started-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch getting-started-theme getting-started-theme",
20 | "upload:hubl": "hs upload getting-started-theme getting-started-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "engines": {
24 | "node": ">=16.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/cms-assets/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/graphql-storybook/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - GQL Storybook",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/components/modules/ContactList/ContactList.stories.jsx:
--------------------------------------------------------------------------------
1 | import { moduleStory } from '@hubspot/cms-dev-server/storybook';
2 |
3 | import { Component, fields } from './index.tsx';
4 |
5 | export default {
6 | title: 'ContactTable',
7 | component: Component,
8 | };
9 |
10 | export const Default = moduleStory(Component, fields, {
11 | dataQueryResult: {
12 | data: {
13 | CRM: {
14 | contact_collection: {
15 | items: [
16 | {
17 | firstname: 'Cool',
18 | lastname: 'Robot',
19 | email: 'coolrobot@hubspot.com',
20 | company: 'HubSpot',
21 | _metadata: { id: 1 },
22 | },
23 | {
24 | firstname: 'Brian',
25 | lastname: 'Halligan',
26 | email: 'bh@hubspot.com',
27 | company: 'HubSpot',
28 | _metadata: { id: 2 },
29 | },
30 | ],
31 | },
32 | },
33 | },
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/components/modules/ContactList/fields.tsx:
--------------------------------------------------------------------------------
1 | import { ModuleFields, ChoiceField } from '@hubspot/cms-components/fields';
2 |
3 | export const fields = (
4 |
5 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gql-storybook-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@emotion/react": "^11.11.0",
7 | "@emotion/styled": "^11.11.0",
8 | "@hubspot/cms-components": "latest",
9 | "@mui/material": "^5.13.0",
10 | "prop-types": "^15.8.1",
11 | "react": "^18.1.0"
12 | },
13 | "devDependencies": {
14 | "@hubspot/cms-dev-server": "latest",
15 | "@testing-library/react": "^13.4.0",
16 | "@vitejs/plugin-react": "^2.1.0",
17 | "jsdom": "^20.0.1",
18 | "vitest": "^0.24.3"
19 | },
20 | "scripts": {
21 | "start": "hs-cms-dev-server . --storybook",
22 | "test": "vitest"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "jsx": "react-jsx",
10 | "moduleResolution": "node16"
11 | },
12 | "$schema": "https://json.schemastore.org/tsconfig",
13 | "display": "Recommended"
14 | }
15 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/gql-storybook-app/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import { default as react } from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gql-storybook-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-theme/gql-storybook.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 | {% block body %}
8 | {% module "contact_list"
9 | path="@projects/gql-storybook-project/gql-storybook-app/components/modules/ContactList" %}
10 | {% endblock body %}
11 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {% block body %}
23 | {% endblock body %}
24 |
25 | {% block footer %}
26 | {% endblock footer %}
27 |
28 |
29 | {# 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 #}
30 | {{ standard_footer_includes }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/gql-storybook-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - GraphQL Storybook",
3 | "preview_path": "./home.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/graphql-storybook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-graphql-storybook",
3 | "description": "JS Building Blocks GrapQL and Storybook",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd gql-storybook-project/gql-storybook-app && yarpm start",
16 | "lint:js": "eslint . --ext .js,.jsx",
17 | "prettier": "prettier . --check",
18 | "watch:hubl": "hs watch gql-storybook-theme gql-storybook-theme",
19 | "upload:hubl": "hs upload gql-storybook-theme gql-storybook-theme",
20 | "deploy": "hs project upload"
21 | },
22 | "dependencies": {},
23 | "engines": {
24 | "node": ">=16.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/hello-world/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/hello-world-app/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/hello-world/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/hello-world/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/assets/huayra.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/hello-world/hello-world-project/hello-world-app/assets/huayra.jpg
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/assets/orange-line.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/hello-world/hello-world-project/hello-world-app/assets/orange-line.jpg
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/assets/tacoma.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/hello-world/hello-world-project/hello-world-app/assets/tacoma.jpg
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/assets/vw-id.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/hello-world/hello-world-project/hello-world-app/assets/vw-id.jpg
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Hello World - React",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/HubSpotSprocket.jsx:
--------------------------------------------------------------------------------
1 | function HubSpotSprocket(props) {
2 | return (
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default HubSpotSprocket;
17 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import layoutStyles from '../styles/layout.module.css';
2 |
3 | function Layout({ children }) {
4 | return {children}
;
5 | }
6 |
7 | export default Layout;
8 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/Link.jsx:
--------------------------------------------------------------------------------
1 | function Link(props) {
2 | return {props.title} ;
3 | }
4 |
5 | export default Link;
6 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/islands/Button.jsx:
--------------------------------------------------------------------------------
1 | import styles from '../../styles/todo.module.css';
2 |
3 | function Button(props) {
4 | return (
5 |
11 | {props.children}
12 |
13 | );
14 | }
15 |
16 | export default Button;
17 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/modules/TodoList/fields.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | FieldGroup,
4 | ModuleFields,
5 | BooleanField,
6 | TextField,
7 | ColorField,
8 | NumberField,
9 | } from '@hubspot/cms-components/fields';
10 |
11 | /**
12 | * Here we are defining module fields that will show up for marketers in the page editor so they can customize the module
13 | * We also define default field values
14 | */
15 | export const fields = (
16 |
17 |
18 |
24 |
25 |
26 |
31 |
40 |
41 | );
42 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/modules/TodoList/index.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import TodoList from '../../islands/TodoList.jsx?island';
3 | import Layout from '../../Layout.jsx';
4 |
5 | /**
6 | * Here we are exporting the 3 requirements of a module: Component, fields, and meta
7 | *
8 | * 'props' contains the resolved values from the defined module fields (./fields.jsx)
9 | * This will be the value provided by a marketer in the page editor, or will fallback to the defined default value
10 | * e.g., props = {
11 | * "default_todo": {"text": "Todo Test 1", "completed": false}
12 | * }
13 | *
14 | * is used here to enable client-side interactivty of a component
15 | * the `hydrateOn` prop allows customization of hydration, i.e., hydrate on page load, or hydrate when the component first becomes visible
16 | *
17 | * The Island component takes on the props of the component it is wrapping
18 | * Note: only props that can be serialized are supported
19 | */
20 | export const Component = ({ fieldValues, hublParameters = {} }) => {
21 | const {
22 | default_todo: defaultTodos,
23 | button_color: buttonColor,
24 | complete_todo_opacity: completeTodoOpacity,
25 | } = fieldValues;
26 | const { title } = hublParameters;
27 | return (
28 |
29 | {title || 'Todo'}
30 |
38 |
39 | );
40 | };
41 | export { fields } from './fields.jsx';
42 | export const meta = {
43 | label: `Todo List Module`,
44 | };
45 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/partials/Cars.jsx:
--------------------------------------------------------------------------------
1 | import Layout from '../Layout.jsx';
2 |
3 | import styles from '../../styles/cars.module.css';
4 |
5 | import tacoma from '../../assets/tacoma.jpg';
6 | import vwid from '../../assets/vw-id.jpg';
7 | import orangeLine from '../../assets/orange-line.jpg';
8 | import huayra from '../../assets/huayra.jpg';
9 |
10 | const cars = [
11 | {
12 | year: 2009,
13 | make: 'Toyota',
14 | model: 'Tacoma',
15 | id: 1,
16 | img: tacoma,
17 | contact: {
18 | firstname: 'Byron',
19 | lastname: 'Matto',
20 | },
21 | },
22 | {
23 | year: 1979,
24 | make: 'MBTA',
25 | model: 'Orange Line',
26 | id: 2,
27 | img: orangeLine,
28 | contact: {
29 | firstname: 'Ben',
30 | lastname: 'Briggs',
31 | },
32 | },
33 | {
34 | year: 2021,
35 | make: 'Volkswagen',
36 | model: 'ID',
37 | id: 3,
38 | img: vwid,
39 | contact: {
40 | firstname: 'Timothy',
41 | lastname: 'Finley',
42 | },
43 | },
44 | {
45 | year: 2019,
46 | make: 'Pagani',
47 | model: 'Huayra',
48 | id: 4,
49 | img: huayra,
50 | contact: {
51 | firstname: 'Charles',
52 | lastname: 'Boamah',
53 | },
54 | },
55 | ];
56 |
57 | function Cars() {
58 | return (
59 |
60 | {`My Teammates' Cars`}
61 |
62 | {cars.map((car) => {
63 | return (
64 |
65 |
66 |
67 |
68 | {car.contact.firstname} {car.contact.lastname}
69 |
70 |
71 |
72 | {car.year} {car.make} {car.model}
73 |
74 |
{' '}
75 |
76 |
77 |
78 |
79 |
80 |
81 | );
82 | })}
83 |
84 |
85 | );
86 | }
87 |
88 | export default Cars;
89 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/partials/Footer.jsx:
--------------------------------------------------------------------------------
1 | import footerStyles from '../../styles/footer.module.css';
2 |
3 | function Footer() {
4 | return (
5 |
8 | );
9 | }
10 |
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/partials/Header.jsx:
--------------------------------------------------------------------------------
1 | import headerStyles from '../../styles/header.module.css';
2 |
3 | function Header({
4 | brandColor = {
5 | opacity: 100,
6 | color: '#FF7A59',
7 | },
8 | text = 'Hello from React!',
9 | }) {
10 | const navLinks = [
11 | {
12 | href: '/hello-world-home',
13 | label: 'Home',
14 | },
15 | {
16 | href: '/hello-world-todo',
17 | label: 'Todo',
18 | },
19 | {
20 | href: '/hello-world-cars',
21 | label: 'Cars',
22 | },
23 | ];
24 |
25 | return (
26 |
44 | );
45 | }
46 |
47 | export default Header;
48 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/components/partials/Home.jsx:
--------------------------------------------------------------------------------
1 | import homeStyles from '../../styles/home.module.css';
2 |
3 | import Layout from '../Layout.jsx';
4 | import HubSpotSprocket from '../HubSpotSprocket';
5 |
6 | function Home() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default Home;
17 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0"
9 | },
10 | "devDependencies": {
11 | "@hubspot/cms-dev-server": "latest",
12 | "@testing-library/react": "^13.4.0",
13 | "@vitejs/plugin-react": "^2.1.0",
14 | "jsdom": "^20.0.1",
15 | "vitest": "^0.24.3"
16 | },
17 | "scripts": {
18 | "start": "hs-cms-dev-server .",
19 | "test": "vitest"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/styles/cars.module.css:
--------------------------------------------------------------------------------
1 | .cars {
2 | display: flex;
3 | flex-wrap: wrap;
4 | width: 100%;
5 | max-width: 800px;
6 | }
7 |
8 | .carGrid {
9 | width: calc(100% / 2);
10 | flex-shrink: 0;
11 | }
12 |
13 | .car {
14 | margin: 5px;
15 | border: 2px solid #ff7a59;
16 | border-radius: 4px;
17 | padding: 14px 7px;
18 | display: flex;
19 | }
20 |
21 | .car h2 {
22 | font-size: 1em;
23 | }
24 |
25 | .carDetails {
26 | height: 100px;
27 | margin-left: auto;
28 | }
29 |
30 | .carDetails img {
31 | margin: 0;
32 | height: 100%;
33 | width: auto;
34 | }
35 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/styles/footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | text-align: center;
3 | font-weight: bold;
4 | font-size: 2em;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/styles/header.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | text-align: center;
3 | font-weight: bold;
4 | font-size: 1.6em;
5 | }
6 |
7 | .header h1 {
8 | font-size: 2em;
9 | margin-top: 0;
10 | }
11 |
12 | .nav {
13 | display: flex;
14 | }
15 |
16 | .nav a {
17 | flex: 0;
18 | margin: 10px;
19 | padding: 10px;
20 | border-width: 0;
21 | border-style: solid;
22 | border-bottom-width: 1px;
23 | text-decoration: none;
24 | }
25 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/styles/home.module.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | margin: auto;
3 | height: auto;
4 | width: 40vw;
5 |
6 | animation-name: spin;
7 | animation-duration: 5000ms;
8 | animation-iteration-count: infinite;
9 | animation-timing-function: linear;
10 | }
11 |
12 | @keyframes spin {
13 | from {
14 | transform: rotate3d(0, 1, 0, 0deg);
15 | }
16 | to {
17 | transform: rotate3d(0, 1, 0, 360deg);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/styles/layout.module.css:
--------------------------------------------------------------------------------
1 | :global(html) {
2 | height: 100%;
3 | }
4 |
5 | :global(body) {
6 | font-family: Arial, Helvetica, sans-serif;
7 | height: 100%;
8 | margin: 0;
9 | padding: 0;
10 | }
11 |
12 | :global(.body-wrapper) {
13 | height: 100%;
14 | display: flex;
15 | flex-direction: column;
16 | }
17 |
18 |
19 | :global(.todo-js-module),
20 | .layout {
21 | flex: 1;
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | flex-direction: column;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/tests/components/__snapshots__/partials.home.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1
2 |
3 | exports[`Home > renders Home 1`] = `
4 |
5 |
32 |
33 | `;
34 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/tests/components/partials.home.test.jsx:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from 'vitest';
2 | import { render } from '@testing-library/react';
3 |
4 | import Home from '../../components/partials/Home.jsx';
5 |
6 | describe('Home', () => {
7 | it('renders Home', () => {
8 | const { asFragment } = render( );
9 |
10 | expect(asFragment()).toMatchSnapshot();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hello-world-app/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import { default as react } from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/cars.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% js_partial
11 | path="@projects/hello-world-project/hello-world-app/components/partials/Cars.jsx",
12 | no_wrapper=True %}
13 |
14 | {% endblock body %}
15 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/home.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% js_partial
11 | path="@projects/hello-world-project/hello-world-app/components/partials/Home.jsx",
12 | no_wrapper=True %}
13 |
14 | {% dnd_area "dnd_area", class="main" %}
15 | {% dnd_section %}
16 |
17 | {% end_dnd_section %}
18 | {% end_dnd_area %}
19 |
20 | {% endblock body %}
21 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% js_partial
21 | path="@projects/hello-world-project/hello-world-app/components/partials/Header.jsx",
22 | brandColor="{{ theme.brand_color }}",
23 | text="Hello from HubL!",
24 | no_wrapper=True %}
25 | {% endblock header %}
26 |
27 | {# 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 #}
28 |
29 | {% block body %}
30 | {% endblock body %}
31 |
32 | {% block footer %}
33 | {% js_partial
34 | path="@projects/hello-world-project/hello-world-app/components/partials/Footer.jsx",
35 | no_wrapper=True %}
36 | {% endblock footer %}
37 |
38 |
39 | {# 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 #}
40 | {{ standard_footer_includes }}
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Hello World",
3 | "preview_path": "./home.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/hello-world/hello-world-theme/todo.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {# Can also use the module tag:
11 | {% module "todo_js_module"
12 | path="@projects/hello-world-project/hello-world-app/components/modules/TodoList"
13 | %}
14 | #}
15 |
16 | {% dnd_area "dnd_area", class="main" %}
17 | {% dnd_section %}
18 | {% dnd_module extra_classes="todo-js-module", path="@projects/hello-world-project/hello-world-app/components/modules/TodoList" %}
19 | {% end_dnd_module %}
20 | {% end_dnd_section %}
21 | {% end_dnd_area %}
22 |
23 | {% endblock body %}
24 |
--------------------------------------------------------------------------------
/examples/hello-world/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/hello-world/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-hello-world",
3 | "description": "JS Building Blocks Hello World",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd hello-world-project/hello-world-app && yarpm start",
16 | "postinstall": "cd hello-world-project/hello-world-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch hello-world-theme hello-world-theme",
20 | "upload:hubl": "hs upload hello-world-theme hello-world-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "dependencies": {},
24 | "engines": {
25 | "node": ">=16.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/islands/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/cms-assets/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/islands/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/islands/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/islands/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
--------------------------------------------------------------------------------
/examples/islands/islands-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "islands-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/Globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Islands",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/ButtonCounter/ButtonCounter.module.css:
--------------------------------------------------------------------------------
1 | .hydrationText {
2 | font-style: italic;
3 | color: #aaa;
4 | margin-left: 1em;
5 | visibility: hidden;
6 | }
7 |
8 | .hydrated .hydrationText {
9 | visibility: visible;
10 | }
11 |
12 | .button {
13 | font-size: inherit;
14 | }
15 |
16 | .button:hover:not(:disabled) {
17 | cursor: pointer;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/ButtonCounter/ButtonCounter.tsx:
--------------------------------------------------------------------------------
1 | import { useState, CSSProperties } from 'react';
2 | import { useAfterIslandHydration } from '@hubspot/cms-components';
3 | import classes from './ButtonCounter.module.css';
4 |
5 | type ButtonCounterProps = {
6 | defaultCount: number;
7 | style: CSSProperties;
8 | };
9 |
10 | type DisabledProps = {
11 | disabled?: boolean;
12 | };
13 |
14 | const ButtonCounter = (props: ButtonCounterProps) => {
15 | const { defaultCount = 0, style } = props;
16 | const [count, setCount] = useState(defaultCount);
17 | const afterHydration = useAfterIslandHydration();
18 |
19 | let disabledProps: DisabledProps = { disabled: true };
20 |
21 | if (afterHydration) {
22 | disabledProps = {};
23 | }
24 |
25 | return (
26 | <>
27 | This is a button!
28 |
29 | setCount((count) => count + 1)}
33 | {...disabledProps}
34 | >
35 | count is: {count}
36 |
37 | hydrated
38 |
39 | >
40 | );
41 | };
42 |
43 | export default ButtonCounter;
44 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/ButtonCounter/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from './ButtonCounter';
2 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/Clock.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export default function Clock({ startTimestamp, enabled = true }) {
4 | // If curious you can see the imlementation details of useCurrTimestamp below
5 | // but for the sake of starting to understand islands, those details are not critical.
6 | const timestamp = useCurrentTimestamp(startTimestamp, enabled);
7 |
8 | const labelSuffix = enabled ? '(enabled)' : '(disabled)';
9 | return (
10 | <>
11 | Time {labelSuffix}: {new Date(timestamp).toLocaleTimeString()}
12 | >
13 | );
14 | }
15 |
16 | // React hook that:
17 | // - Takes an initial timestamp and enabled boolean flag
18 | // - On the server, it will always return the initial timestamp
19 | // - But in the browser, if enabled, it will return newly updated timestamp every second
20 | function useCurrentTimestamp(startTimestamp, enabled) {
21 | const [currTimestamp, setCurrTimestamp] = useState(startTimestamp);
22 |
23 | // This effect that updates the current timestamp state every second will only
24 | // run in the browser.
25 | useEffect(() => {
26 | if (enabled) {
27 | const intervalId = setInterval(() => setCurrTimestamp(Date.now(), 1000));
28 |
29 | // Cleanup function to stop the timer if/when this component is unmounted
30 | return function cleanup() {
31 | clearInterval(intervalId);
32 | };
33 | }
34 | }, [enabled]);
35 |
36 | return currTimestamp;
37 | }
38 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/partials/BasicIslandClock.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import ClockIsland from '../Clock?island';
3 |
4 | export default function BasicIslandClock() {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/partials/IslandsTester.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 |
3 | import ButtonCounterIsland from '../ButtonCounter?island';
4 |
5 | const IslandsTester = ({
6 | numIslands = 1,
7 | marginTop,
8 | marginBetween = '50vh',
9 | hydrateOn = 'load',
10 | islandIdPrefix = '',
11 | }) => {
12 | numIslands = parseInt(numIslands, 10);
13 |
14 | const islandElements = new Array(numIslands).fill('').map((_, i) => {
15 | let style = { marginTop: marginTop ?? marginBetween };
16 | if (i === 0 && marginTop == null) {
17 | delete style.marginTop;
18 | }
19 |
20 | const islandId = `${islandIdPrefix}island-${i}`;
21 |
22 | return (
23 |
31 | );
32 | });
33 | return {islandElements}
;
34 | };
35 |
36 | export default IslandsTester;
37 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/components/partials/IslandsTesterPlus.jsx:
--------------------------------------------------------------------------------
1 | import IslandTester from './IslandsTester';
2 |
3 | const IslandsColumn = ({ numIslands, marginBetween, hydrateOn }) => {
4 | return (
5 |
6 |
On {hydrateOn}
7 |
13 |
14 | );
15 | };
16 | const IslandsTesterPlus = ({ numIslands = 1, marginBetween = '50vh' }) => {
17 | return (
18 |
19 |
24 |
30 |
35 |
36 | );
37 | };
38 |
39 | export default IslandsTesterPlus;
40 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "islands-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0",
9 | "typescript-plugin-css-modules": "^5.0.1"
10 | },
11 | "devDependencies": {
12 | "@hubspot/cms-dev-server": "latest",
13 | "@testing-library/react": "^13.4.0",
14 | "@vitejs/plugin-react": "^2.1.0",
15 | "jsdom": "^20.0.1",
16 | "vitest": "^0.24.3"
17 | },
18 | "scripts": {
19 | "start": "hs-cms-dev-server .",
20 | "test": "vitest"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "commonjs",
5 | "moduleResolution": "node16",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "jsx": "react-jsx",
11 | "plugins": [
12 | {
13 | "name": "typescript-plugin-css-modules"
14 | }
15 | ]
16 | },
17 | "$schema": "https://json.schemastore.org/tsconfig",
18 | "display": "Recommended"
19 | }
20 |
--------------------------------------------------------------------------------
/examples/islands/islands-project/islands-app/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import { default as react } from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/islands/islands-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/islands/islands-theme/islands.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 | {% js_partial path="@projects/islands-project/islands-app/components/partials/BasicIslandClock.jsx" %}
10 |
11 | {% js_partial
12 | path="@projects/islands-project/islands-app/components/partials/IslandsTesterPlus.jsx",
13 | numIslands="12" %}
14 |
15 | {% endblock body %}
16 |
--------------------------------------------------------------------------------
/examples/islands/islands-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {% block body %}
23 | {% endblock body %}
24 |
25 | {% block footer %}
26 | {% endblock footer %}
27 |
28 |
29 | {# 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 #}
30 | {{ standard_footer_includes }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/islands/islands-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Islands",
3 | "preview_path": "./islands.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/islands/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-hello-world",
3 | "description": "JS Building Blocks Hello World",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd islands-project/islands-app && yarpm start --",
16 | "postinstall": "cd islands-project/islands-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch islands-theme islands-theme",
20 | "upload:hubl": "hs upload islands-theme islands-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "engines": {
24 | "node": ">=16.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/module-components/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/hello-world-app/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/module-components/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/module-components/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/module-components/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "module-components-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/module-components-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Components - CMS React",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/module-components-app/components/modules/IconExample/index.jsx:
--------------------------------------------------------------------------------
1 | import { ModuleFields, IconField } from '@hubspot/cms-components/fields';
2 | import { Icon } from '@hubspot/cms-components';
3 |
4 | export function Component() {
5 | return (
6 | <>
7 | Icon Examples
8 |
15 | {/* The Icon component takes a field path as defined in the "fields" export */}
16 |
17 |
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | export const fields = (
25 |
26 |
33 |
40 |
47 |
48 | );
49 |
50 | export const meta = {
51 | label: 'Icon Example',
52 | };
53 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/module-components-app/components/modules/RichTextExample/index.jsx:
--------------------------------------------------------------------------------
1 | import { ModuleFields, RichTextField } from '@hubspot/cms-components/fields';
2 | import { RichText, logInfo } from '@hubspot/cms-components';
3 |
4 | export function Component(props) {
5 | logInfo(props);
6 |
7 | return (
8 | <>
9 | Rich Text Example
10 |
15 |
16 |
17 | >
18 | );
19 | }
20 |
21 | export const fields = (
22 |
23 |
28 |
29 | );
30 | export const meta = {
31 | label: 'Rich Text Example',
32 | };
33 |
--------------------------------------------------------------------------------
/examples/module-components/module-components-project/module-components-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "module-components-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0"
9 | },
10 | "devDependencies": {
11 | "@hubspot/cms-dev-server": "latest",
12 | "@testing-library/react": "^13.4.0",
13 | "@vitejs/plugin-react": "^2.1.0",
14 | "jsdom": "^20.0.1",
15 | "vitest": "^0.24.3"
16 | },
17 | "scripts": {
18 | "start": "hs-cms-dev-server .",
19 | "test": "vitest"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/module-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "module-components",
3 | "description": "CMS React Module Components Example",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd module-components-project/module-components-app && yarpm start",
16 | "postinstall": "cd module-components-project/module-components-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "deploy": "hs project upload"
20 | },
21 | "dependencies": {},
22 | "engines": {
23 | "node": ">=16.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/routing/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/cms-assets/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/routing/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
4 |
--------------------------------------------------------------------------------
/examples/routing/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/routing/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
13 |
--------------------------------------------------------------------------------
/examples/routing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-react-routing",
3 | "description": "CMS React - Routing",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd routing-project/routing-app && yarpm start --",
16 | "postinstall": "cd routing-project/routing-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch routing-theme routing-theme",
20 | "upload:hubl": "hs upload routing-theme routing-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "engines": {
24 | "node": ">=16.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "routing-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/Global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css';
2 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/assets/dynamic-slug-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/routing/routing-project/routing-app/assets/dynamic-slug-screenshot.png
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React - Routing",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from 'react-router-dom';
2 | import navigationStyles from '../styles/header.module.css';
3 | import pokeball from '../assets/pokeball.svg';
4 |
5 | export default function Header() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 | isActive ? navigationStyles.active : undefined
18 | }
19 | >
20 | Home
21 |
22 |
23 |
24 |
27 | isActive ? navigationStyles.active : undefined
28 | }
29 | >
30 | Pokedex
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/Home.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import pageStyles from '../styles/page.module.css';
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
CMS React Routing
8 | See Pokedex
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/ListingCard.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import cardStyles from '../styles/card.module.css';
3 |
4 | export function ListingCard({ pokemonList }: { pokemonList: any }) {
5 | return (
6 |
7 | {pokemonList.map((pokemon) => (
8 |
9 |
10 |
15 |
16 |
{pokemon.pokemon_v2_pokemontypes[0].pokemon_v2_type.name}
17 |
{pokemon.name}
18 |
19 |
20 |
21 | ))}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/Pokedex.tsx:
--------------------------------------------------------------------------------
1 | import pageStyles from '../styles/page.module.css';
2 | import { ListingCard } from './ListingCard.tsx';
3 |
4 | const POKEMON_GRAPHQL_SCHEMA_URL = 'https://beta.pokeapi.co/graphql/v1beta/';
5 |
6 | export default function Pokedex({ pokemonList }: { pokemonList: any }) {
7 | return (
8 |
9 | Pokedex
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/Pokemon.tsx:
--------------------------------------------------------------------------------
1 | import { Link, useParams } from 'react-router-dom';
2 | import pageStyles from '../styles/page.module.css';
3 | import ProfileCard from './ProfileCard.tsx';
4 |
5 | export default function Pokemon({ pokemonList }: { pokemonList: any }) {
6 | const params = useParams();
7 | const pokemon = pokemonList.find((pokemon) => pokemon.name === params.name);
8 | return (
9 |
10 | Profile
11 |
12 |
13 | Back to Pokedex
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/ProfileCard.tsx:
--------------------------------------------------------------------------------
1 | import pageStyles from '../styles/page.module.css';
2 |
3 | export default function ProfileCard({ pokemon }: any) {
4 | const {
5 | name,
6 | height,
7 | weight,
8 | base_experience,
9 | pokemon_v2_pokemonmoves: moves,
10 | pokemon_v2_pokemonsprites: sprites,
11 | pokemon_v2_pokemontypes: types,
12 | } = pokemon;
13 |
14 | const profileImageSrc = sprites[0].sprites;
15 |
16 | const listOfMoves = moves.map((move: any) => (
17 | {move.pokemon_v2_move.name}
18 | ));
19 |
20 | const listOfTypes = types.map((type: any) => (
21 | {type.pokemon_v2_type.name}
22 | ));
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
{name}
30 |
31 |
32 | STATS
33 | HP: {base_experience}
34 | HEIGHT: {height}m
35 | WEIGHT: {weight}kg
36 |
37 |
38 |
39 |
40 |
41 |
TYPES
42 | {listOfTypes}
43 |
44 |
45 |
MOVES
46 | {listOfMoves}
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/islands/App.tsx:
--------------------------------------------------------------------------------
1 | import { Routes, Route, BrowserRouter, Link } from 'react-router-dom';
2 | import { StaticRouter } from 'react-router-dom/server';
3 | import {
4 | useIsServerRender,
5 | usePageUrl,
6 | useBasePath,
7 | } from '@hubspot/cms-components';
8 | import Header from '../Header.tsx';
9 | import Pokedex from '../Pokedex.tsx';
10 | import Pokemon from '../Pokemon.tsx';
11 | import Home from '../Home.tsx';
12 | import { pokemonList } from '../../constants.ts';
13 |
14 | const AppRoutes = () => {
15 | return (
16 |
17 | } />
18 | } />
19 | }
22 | />
23 |
24 | );
25 | };
26 |
27 | const App = () => {
28 | const isServerRender = useIsServerRender();
29 | const pageUrl = usePageUrl();
30 | const basePath = useBasePath();
31 |
32 | let app: JSX.Element;
33 |
34 | if (isServerRender) {
35 | app = (
36 |
37 |
38 |
39 |
40 | );
41 | } else {
42 | app = (
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | return app;
51 | };
52 |
53 | export default App;
54 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/components/modules/Router/index.tsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import AppIsland from '../../islands/App?island';
3 |
4 | export const Component = () => {
5 | return ;
6 | };
7 |
8 | export const fields = [];
9 |
10 | export const meta = {
11 | label: 'Router Module',
12 | };
13 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "routing-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0",
9 | "react-router-dom": "^6.18.0",
10 | "graphql": "^16.8.1",
11 | "graphql-request": "^6.1.0"
12 | },
13 | "devDependencies": {
14 | "@hubspot/cms-dev-server": "latest",
15 | "@vitejs/plugin-react": "^2.1.0",
16 | "jsdom": "^20.0.1",
17 | "vitest": "^0.24.3"
18 | },
19 | "scripts": {
20 | "start": "hs-cms-dev-server ."
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/styles/card.module.css:
--------------------------------------------------------------------------------
1 | .cards {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: center;
5 | align-items: flex-start;
6 | gap: 15px;
7 | }
8 |
9 | .cards a {
10 | text-decoration: none;
11 | width: 30%;
12 | min-width: 300px;
13 | }
14 |
15 | .card {
16 | background-color: black;
17 | color: white;
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | gap: 25px;
22 | padding: 10px 15px;
23 | border-radius: 5px;
24 | height: 125px;
25 | cursor: pointer;
26 | }
27 |
28 | .card p,
29 | .card h2 {
30 | margin: 0;
31 | }
32 |
33 | .card img {
34 | border-radius: 5px;
35 | }
36 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/styles/header.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | justify-content: space-between;
7 | font-family: 'Mono', monospace;
8 | gap: 20px;
9 | padding: 15px 0;
10 | }
11 |
12 | .header nav ul {
13 | list-style: none;
14 | display: flex;
15 | flex-direction: row;
16 | align-items: center;
17 | gap: 30px;
18 | margin: 0;
19 | padding: 0;
20 | }
21 |
22 | .header nav ul li a {
23 | color: #33475b;
24 | text-decoration: none;
25 | padding-bottom: 4px;
26 | transition: all 0.2s ease;
27 | border-bottom: 2px solid transparent;
28 | }
29 |
30 | .header nav ul li a:hover {
31 | color: #ff7a59;
32 | border-bottom: 2px solid #ff7a59;
33 | transition: all 0.3s ease;
34 | }
35 |
36 | .header nav ul li a.active {
37 | color: #ff7a59 !important;
38 | border-bottom: 2px solid #ff7a59;
39 | }
40 |
41 | .header .btn {
42 | list-style: none;
43 | background-color: #33475b;
44 | padding: 10px 20px;
45 | border: none;
46 | border-radius: 4px;
47 | cursor: pointer;
48 | transition: all 0.2s ease;
49 | }
50 |
51 | .header .btn:hover {
52 | background-color: #ff7a59;
53 | transition: all 0.3s ease;
54 | }
55 |
56 | .header .btn a {
57 | text-decoration: none;
58 | color: white;
59 | }
60 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/styles/page.module.css:
--------------------------------------------------------------------------------
1 | .page {
2 | font-family: 'Mono', monospace;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | margin-top: 40px;
8 | }
9 |
10 | .page h1 {
11 | margin-top: 0;
12 | }
13 |
14 | .page .back {
15 | text-align: center;
16 | }
17 |
18 | .page ul {
19 | list-style-type: none;
20 | padding: 0;
21 | }
22 |
23 | .page ul li {
24 | margin-bottom: 10px;
25 | }
26 |
27 | .profile {
28 | margin: 50px auto;
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | gap: 50px;
33 | }
34 |
35 | .home {
36 | width: 100%;
37 | text-align: center;
38 | }
39 |
40 | .home h1 {
41 | margin-bottom: 50px;
42 | }
43 |
44 | .home a {
45 | text-decoration: none;
46 | color: white;
47 | background-color: red;
48 | padding: 12px 15px;
49 | border-radius: 5px;
50 | }
51 |
52 | .attributes {
53 | display: flex;
54 | align-items: center;
55 | justify-content: space-between;
56 | flex-direction: column;
57 | gap: 20px;
58 | }
59 |
60 | .attributes > div {
61 | display: flex;
62 | flex-direction: row;
63 | gap: 25px;
64 | }
65 |
66 | .stacked {
67 | display: flex;
68 | flex-direction: column;
69 | }
70 |
71 | .top {
72 | display: flex;
73 | flex-direction: row;
74 | }
75 |
76 | .card {
77 | background-color: #3e2073;
78 | border-radius: 15px;
79 | padding: 25px 50px 25px 15px;
80 | width: 600px;
81 | color: #f0e68c;
82 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
83 | display: flex;
84 | justify-content: space-between;
85 | gap: 50px;
86 | margin-bottom: 40px;
87 | }
88 |
89 | .card img {
90 | height: auto;
91 | }
92 |
93 | .card .info {
94 | flex-grow: 1;
95 | }
96 |
97 | .moves {
98 | display: flex;
99 | flex-direction: column;
100 | gap: 20px;
101 | }
102 |
103 | .attributeTitle {
104 | padding: 5px;
105 | background-color: #f0e68c;
106 | color: #3e2073;
107 | }
108 |
--------------------------------------------------------------------------------
/examples/routing/routing-project/routing-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "allowImportingTsExtensions": true,
10 | "noEmit": true,
11 | "isolatedModules": true,
12 | "jsx": "react-jsx",
13 | "skipLibCheck": true,
14 | "types": ["vite/client", "vitest/globals"]
15 | },
16 | "include": ["components/**/*"],
17 | "exclude": ["node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/routing/routing-theme/fields.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/examples/routing/routing-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {# 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 #}
23 |
24 | {% block body %}
25 | {% endblock body %}
26 |
27 | {% block footer %}
28 | {% endblock footer %}
29 |
30 |
31 | {# 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 #}
32 | {{ standard_footer_includes }}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/routing/routing-theme/router-page.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module "router-module"
11 | path="@projects/routing-project/routing-app/components/modules/Router"
12 | %}
13 |
14 | {% endblock body %}
15 |
--------------------------------------------------------------------------------
/examples/routing/routing-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React - Routing"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/serverless/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/hello-world-app/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/serverless/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/serverless/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/serverless/README.md:
--------------------------------------------------------------------------------
1 | # CMS React Module with Serverless Functions Example
2 |
3 | This directory contains an example of a CMS React Module that makes use of serverless functions on the new HubSpot developer platform.
4 |
5 | ## Overview
6 |
7 | This example demonstrates how to build a CMS React Module that leverages serverless functions to enhance the functionality of your HubSpot CMS. By combining the power of React and serverless functions, you can create dynamic and interactive modules that seamlessly integrate with the HubSpot developer platform.
8 |
9 | ## Resources
10 |
11 | For more information about CMS React Modules and serverless functions on the HubSpot developer platform, refer to the following resources:
12 |
13 | - [HubSpot CMS Developer Documentation](https://developers.hubspot.com/cms/)
14 | - [React Documentation](https://reactjs.org/)
15 | - [HubSpot Developer Platform Serverless Documentation](https://developers.hubspot.com/docs/platform/serverless-functions)
16 |
17 | ## License
18 |
19 | This example is licensed under the [MIT License](LICENSE).
20 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/app/app.functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app.functions",
3 | "version": "1.0.0",
4 | "description": "",
5 | "dependencies": {}
6 | }
7 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/app/app.functions/parrot-function.js:
--------------------------------------------------------------------------------
1 | exports.main = async (context) => {
2 | return {
3 | statusCode: 200,
4 | body: {
5 | message: `SQUAWK: ${context.params.message}`,
6 | },
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/app/app.functions/serverless.json:
--------------------------------------------------------------------------------
1 | {
2 | "appFunctions": {
3 | "parrotFunction": {
4 | "file": "parrot-function.js",
5 | "endpoint": {
6 | "path": "parrot",
7 | "method": ["GET"]
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/app/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CMS React With Serverless",
3 | "description": "This app contains a serverless function wich can parrot requests",
4 | "scopes": [
5 | "crm.objects.companies.read",
6 | "crm.objects.companies.write",
7 | "collector.graphql_schema.read",
8 | "collector.graphql_query.execute",
9 | "crm.objects.custom.write",
10 | "crm.objects.custom.read",
11 | "crm.schemas.custom.read",
12 | "crm.schemas.custom.write"
13 | ],
14 | "uid": "cms_react_serverless_app",
15 | "public": false
16 | }
17 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/cms-react/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "CMS React",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/cms-react/components/modules/MakeServerlessRequest/MakeServerlessRequestIsland.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default function MakeServerlessRequestIsland() {
4 | const [data, setData] = useState([]);
5 | const [message, setMessage] = useState('');
6 |
7 | function makeRequestToProjectFunction(event) {
8 | event.preventDefault();
9 |
10 | return fetch(`/hs/serverless/parrot?message=${message}`)
11 | .then((response) => response.json())
12 | .then((jsonResponse) => {
13 | setData([...data, jsonResponse.message]);
14 | });
15 | }
16 |
17 | return (
18 | <>
19 |
37 |
44 | {data.map((item) => (
45 |
46 | {item}
47 |
48 | ))}
49 |
50 | >
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/cms-react/components/modules/MakeServerlessRequest/index.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 |
3 | import MakeServerlessRequestIsland from './MakeServerlessRequestIsland?island';
4 |
5 | export function Component() {
6 | return (
7 |
8 |
Parrot Island
9 |
13 |
14 | );
15 | }
16 |
17 | export const fields = [];
18 |
19 | export const meta = {
20 | label: 'Make Serverless Requests',
21 | };
22 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/cms-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "module-components-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "prop-types": "^15.8.1",
8 | "react": "^18.1.0"
9 | },
10 | "devDependencies": {
11 | "@hubspot/cms-dev-server": "latest",
12 | "@testing-library/react": "^13.4.0",
13 | "@vitejs/plugin-react": "^2.1.0",
14 | "jsdom": "^20.0.1",
15 | "vitest": "^0.24.3"
16 | },
17 | "scripts": {
18 | "start": "hs-cms-dev-server .",
19 | "test": "vitest"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/serverless/cms-with-serverless-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-with-serverless-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/serverless/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/serverless/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cms-with-serverless",
3 | "description": "CMS React Module Components Example",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd cms-with-serverless-project/src/cms-react && yarpm start",
16 | "postinstall": "cd cms-with-serverless-project/src/cms-react && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "deploy": "hs project upload"
20 | },
21 | "dependencies": {},
22 | "engines": {
23 | "node": ">=16.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/styling/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/cms-assets/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/styling/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/styling/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/styling/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
--------------------------------------------------------------------------------
/examples/styling/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-styling",
3 | "description": "JS Building Blocks Styling",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd styling-project/styling-app && yarpm start",
16 | "postinstall": "cd styling-project/styling-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch styling-theme styling-theme",
20 | "upload:hubl": "hs upload styling-theme styling-theme",
21 | "deploy": "hs project upload",
22 | "test": "cd styling-project && cd styling-app && yarpm run test"
23 | },
24 | "engines": {
25 | "node": ">=16.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styling-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/assets/biker-tailwind-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/styling/styling-project/styling-app/assets/biker-tailwind-ai.png
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/assets/dog-named-tailwind-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/styling/styling-project/styling-app/assets/dog-named-tailwind-ai.png
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/assets/windy-trail-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HubSpot/cms-react/6f4a05d12d121080d518363fc5643e90a80c6ab7/examples/styling/styling-project/styling-app/assets/windy-trail-ai.png
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Styling",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/InteractiveStyledComponent.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useId } from 'react';
2 | import { styled } from 'styled-components';
3 |
4 | const StyledButton = styled.button`
5 | padding: ${props => 10 + props.$count * 10}px;
6 | `;
7 |
8 | export default function InteractiveStyledComponent() {
9 | const id = useId();
10 | const [count, setCount] = useState(0);
11 |
12 | return (
13 | setCount(prevCount => prevCount + 1)}>Current count is: {count} {id}
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/InteractiveStyledJSXComponent.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useId } from 'react';
2 |
3 | export default function InteractiveStyledJSXComponent() {
4 | const id = useId();
5 | const [count, setCount] = useState(0);
6 |
7 | return (
8 | setCount(prevCount => prevCount + 1)}>
9 | Current count is: {count} {id}
10 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/StyledComponentsIsland.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import StyledComponentsRegistry from './StyledComponentsRegistry?client';
3 | // Important! Wrapper expects a lazy component, which you can do easily with `?client` similar to `?island`
4 |
5 | export default function StyledComponentsIsland(props) {
6 | // Using the `Wrapper` prop for `Island` to wrap the contents in a
7 | // `StyledComponentsRegistry` to capture generated styles on the server
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/StyledComponentsRegistry.jsx:
--------------------------------------------------------------------------------
1 | import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
2 | import { useInlineHeadAsset } from '@hubspot/cms-components';
3 |
4 | export default function StyledComponentsRegistry({ children }) {
5 | // On the client, styled-components creates its own stylesheet. We only want
6 | // to create this sheet on the server
7 | const styledComponentsStyleSheet = import.meta.env.SSR
8 | ? new ServerStyleSheet()
9 | : null;
10 |
11 | useInlineHeadAsset(() => {
12 | if (styledComponentsStyleSheet === null) {
13 | return;
14 | }
15 |
16 | // Collect styles generated on the server pass and return them to go in the
17 | //
18 | const styles = styledComponentsStyleSheet.getStyleElement();
19 | styledComponentsStyleSheet.seal();
20 | return <>{styles}>;
21 | });
22 |
23 | if (styledComponentsStyleSheet === null) {
24 | return <>{children}>;
25 | }
26 |
27 | return (
28 |
29 | {children}
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/StyledJSXIsland.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import StyledJSXRegistry from './StyledJSXRegistry?client';
3 | // Important! Wrapper expects a lazy component, which you can do easily with `?client` similar to `?island`
4 |
5 | export default function StyledJSXIsland(props) {
6 | // Using the `Wrapper` prop for `Island` to wrap the contents in a
7 | // `StyledJSXRegistry` to capture generated styles on the server
8 | return
9 | }
10 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/StyledJSXRegistry.jsx:
--------------------------------------------------------------------------------
1 | import { StyleRegistry, useStyleRegistry } from 'styled-jsx';
2 | import { useInlineHeadAsset } from '@hubspot/cms-components';
3 |
4 | function InsertHTML() {
5 | const registry = useStyleRegistry();
6 | useInlineHeadAsset(() => {
7 | // Collect styles generated on the server pass and return them to go in the
8 | //
9 |
10 | const styles = registry.styles();
11 | return <>{styles}>;
12 | });
13 | }
14 |
15 | export default function StyledJSXRegistry({ children }) {
16 | // for styled-jsx, the registry component needs to be included on the client
17 | // and server to make sure that hook usage is preserved. excluding it on the
18 | // client can result in mismatching results of useId()
19 |
20 | return (
21 |
22 |
23 | {children}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/islands/ToggleSwitch.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Switch } from '@headlessui/react';
3 |
4 | export default function SomeToggleSwitch({ id, islandId, defaultChecked }) {
5 | id ??= `${islandId}-switch`;
6 |
7 | const [checked, setChecked] = useState(defaultChecked);
8 |
9 | const onChange = () => {
10 | console.log(`toggle ${id} changed`, !checked);
11 | setChecked(!checked);
12 | };
13 | return (
14 |
22 | Enable something
23 |
24 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/CSSModulesPartial.jsx:
--------------------------------------------------------------------------------
1 | import classes from '../../styles/CSSModulesPartial.module.css';
2 |
3 | function CSSModulesPartial({ extraClassNames = '' }) {
4 | return (
5 |
6 |
CSSModulesPartial
7 |
8 | Muffin cake candy cookie fruitcake wafer gummies macaroon. sesame
9 | jujubes powder gummies cupcake macaroon brownie apple pie fruitcake.
10 | gingerbread chupa chups pudding apple pie pudding jujubes marshmallow
11 | powder sugar. toffee cake cupcake fruitcake icing topping halvah powder
12 | candy canes chocolate wafer powder danish powder. lollipop tiramisu
13 | brownie marshmallow donut muffin apple pie candy canes apple pie. oat
14 | cake.
15 |
16 |
17 | Gummies snaps muffin lemon drops bonbon croissant licorice toffee
18 | lollipop sugar plum. sugar plum oat cake cotton candy croissant candy
19 | jujubes ice cream gingerbread biscuit sugar plum fruitcake. apple pie.
20 |
21 |
22 | );
23 | }
24 |
25 | export default CSSModulesPartial;
26 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/CSSPropertiesPartial.jsx:
--------------------------------------------------------------------------------
1 | import '../../styles/CSSPropertiesPartial.css';
2 |
3 | function CSSPropertiesPartial({
4 | wrapperPadding,
5 | wrapperBG,
6 | wrapperBorder,
7 | wrapperColor,
8 | sharedMargin = '2rem',
9 | }) {
10 | return (
11 |
21 |
CSSPropertiesPartial
22 |
23 | Muffin cake candy cookie fruitcake wafer gummies macaroon. sesame
24 | jujubes powder gummies cupcake macaroon brownie apple pie fruitcake.
25 | gingerbread chupa chups pudding apple pie pudding jujubes marshmallow
26 | powder sugar. toffee cake cupcake fruitcake icing topping halvah powder
27 | candy canes chocolate wafer powder danish powder. lollipop tiramisu
28 | brownie marshmallow donut muffin apple pie candy canes apple pie. oat
29 | cake.
30 |
31 |
32 | Gummies snaps muffin lemon drops bonbon croissant licorice toffee
33 | lollipop sugar plum. sugar plum oat cake cotton candy croissant candy
34 | jujubes ice cream gingerbread biscuit sugar plum fruitcake. apple pie.
35 |
36 |
37 | );
38 | }
39 |
40 | export default CSSPropertiesPartial;
41 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/InlineStylesPartial.jsx:
--------------------------------------------------------------------------------
1 | function CSSPropertiesPartial({
2 | wrapperPadding = '1rem',
3 | wrapperBG = 'lightcoral',
4 | wrapperBorder = '2px dotted crimson',
5 | wrapperColor = 'white',
6 | sharedMargin = '1rem',
7 | }) {
8 | return (
9 |
17 |
18 | InlineStylesPartial
19 |
20 |
21 | Muffin cake candy cookie fruitcake wafer gummies macaroon. sesame
22 | jujubes powder gummies cupcake macaroon brownie apple pie fruitcake.
23 | gingerbread chupa chups pudding apple pie pudding jujubes marshmallow
24 | powder sugar. toffee cake cupcake fruitcake icing topping halvah powder
25 | candy canes chocolate wafer powder danish powder. lollipop tiramisu
26 | brownie marshmallow donut muffin apple pie candy canes apple pie. oat
27 | cake.
28 |
29 |
30 | Gummies snaps muffin lemon drops bonbon croissant licorice toffee
31 | lollipop sugar plum. sugar plum oat cake cotton candy croissant candy
32 | jujubes ice cream gingerbread biscuit sugar plum fruitcake. apple pie.
33 |
34 |
35 | );
36 | }
37 |
38 | export default CSSPropertiesPartial;
39 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/StyledComponentsPartial.jsx:
--------------------------------------------------------------------------------
1 | import { styled } from 'styled-components';
2 | import StyledComponentsRegistry from '../StyledComponentsRegistry'
3 | import StyledComponentsIsland from '../StyledComponentsIsland';
4 | import InteractiveStyledComponent from '../InteractiveStyledComponent?island';
5 |
6 | const StyledContainer = styled.div`
7 | padding: 1rem;
8 | background-color: lightcoral;
9 | border: 2px dotted crimson;
10 | color: white;
11 | `;
12 |
13 | const StyledHeader = styled.h2`
14 | margin: 0;
15 | marginBottom: 1rem;
16 | `;
17 |
18 | function StyledComponentsPartial({
19 | }) {
20 | return (
21 |
22 |
23 |
24 | StyledComponentsPartial
25 |
26 |
27 | Muffin cake candy cookie fruitcake wafer gummies macaroon. sesame
28 | jujubes powder gummies cupcake macaroon brownie apple pie fruitcake.
29 | gingerbread chupa chups pudding apple pie pudding jujubes marshmallow
30 | powder sugar. toffee cake cupcake fruitcake icing topping halvah powder
31 | candy canes chocolate wafer powder danish powder. lollipop tiramisu
32 | brownie marshmallow donut muffin apple pie candy canes apple pie. oat
33 | cake.
34 |
35 |
36 | Gummies snaps muffin lemon drops bonbon croissant licorice toffee
37 | lollipop sugar plum. sugar plum oat cake cotton candy croissant candy
38 | jujubes ice cream gingerbread biscuit sugar plum fruitcake. apple pie.
39 |
40 | {/* We need to wrap `Island` in something like `StyledComponentsIsland`
41 | to capture styles from the island subtree */}
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | export default StyledComponentsPartial;
49 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/StyledJSXPartial.jsx:
--------------------------------------------------------------------------------
1 | import StyledJSXRegistry from '../StyledJSXRegistry';
2 | import StyledJSXIsland from '../StyledJSXIsland';
3 | import InteractiveStyledJSXComponent from '../InteractiveStyledJSXComponent?island'
4 |
5 | function StyledJSXPartial() {
6 | return (
7 |
8 |
9 |
10 | StyledJSXPartial
11 |
12 |
13 | Muffin cake candy cookie fruitcake wafer gummies macaroon. sesame
14 | jujubes powder gummies cupcake macaroon brownie apple pie fruitcake.
15 | gingerbread chupa chups pudding apple pie pudding jujubes marshmallow
16 | powder sugar. toffee cake cupcake fruitcake icing topping halvah powder
17 | candy canes chocolate wafer powder danish powder. lollipop tiramisu
18 | brownie marshmallow donut muffin apple pie candy canes apple pie. oat
19 | cake.
20 |
21 |
22 | Gummies snaps muffin lemon drops bonbon croissant licorice toffee
23 | lollipop sugar plum. sugar plum oat cake cotton candy croissant candy
24 | jujubes ice cream gingerbread biscuit sugar plum fruitcake. apple pie.
25 |
26 |
45 | {/* We need to wrap `Island` in something like `StyledComponentsIsland`
46 | to capture styles from the island subtree */}
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default StyledJSXPartial;
54 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/components/partials/TailwindPartial.jsx:
--------------------------------------------------------------------------------
1 | import { Island } from '@hubspot/cms-components';
2 | import BookOpenIcon from '@heroicons/react/24/solid/esm/BookOpenIcon.js';
3 |
4 | import ToggleSwitchIsland from '../islands/ToggleSwitch.jsx?island';
5 |
6 | import biker from '../../assets/biker-tailwind-ai.png';
7 | import dog from '../../assets/dog-named-tailwind-ai.png';
8 | import trail from '../../assets/windy-trail-ai.png';
9 | import '../../styles/tailwind.css';
10 |
11 | const aiArt = [
12 | {
13 | name: 'A Biker With a Tailwind',
14 | image: `${biker}?name=calvin&width=150`,
15 | },
16 | {
17 | name: 'A Dog Named Tailwind',
18 | image: `${dog}?name=kristen&width=150`,
19 | },
20 | {
21 | name: 'The Windiest Trail',
22 | image: `${trail}?name=ted&width=150`,
23 | },
24 | ];
25 |
26 | export default function Example() {
27 | return (
28 | <>
29 |
30 | {aiArt.map((art) => (
31 |
32 |
33 |
36 |
37 | ))}
38 |
39 |
40 |
45 |
46 |
47 | BookOpen
48 |
49 |
50 |
51 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styling-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@headlessui/react": "^1.7.14",
7 | "@heroicons/react": "^2.0.18",
8 | "@hubspot/cms-components": "latest",
9 | "autoprefixer": "^10.4.14",
10 | "postcss-nested": "^6.0.1",
11 | "prop-types": "^15.8.1",
12 | "react": "^18.1.0",
13 | "styled-jsx": "^5.1.2",
14 | "styled-components": "^6.0.3",
15 | "tailwindcss": "^3.3.2"
16 | },
17 | "devDependencies": {
18 | "@hubspot/cms-dev-server": "latest",
19 | "@testing-library/react": "^13.4.0",
20 | "@vitejs/plugin-react": "^2.1.0",
21 | "jsdom": "^20.0.1",
22 | "vitest": "^0.24.3"
23 | },
24 | "scripts": {
25 | "start": "hs-cms-dev-server .",
26 | "test": "vitest"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | import tailwind from 'tailwindcss';
2 | import autoprefixer from 'autoprefixer';
3 | import postcssNested from 'postcss-nested';
4 | import tailwindConfig from './tailwind.config.js';
5 |
6 | export default {
7 | plugins: [tailwind(tailwindConfig), postcssNested, autoprefixer()],
8 | };
9 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/styles/CSSModulesPartial.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
3 | padding: 1rem;
4 | background-color: lightcoral;
5 | border: 2px dotted crimson;
6 | color: white;
7 | }
8 |
9 | .wrapper h2 {
10 | margin: 0 0 1rem 0;
11 | }
12 |
13 | .wrapper {
14 | p + p {
15 | margin-top: 1rem;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/styles/CSSPropertiesPartial.css:
--------------------------------------------------------------------------------
1 | .css-properties-partial {
2 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
3 | Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
4 | padding: var(--wrapper-padding, 1rem);
5 | background-color: var(--wrapper-bg, lightcoral);
6 | border: var(--wrapper-border, 2px dotted crimson);
7 | color: var(--wrapper-color, white);
8 |
9 | --shared-margin: 1rem;
10 | }
11 |
12 | .css-properties-partial h2 {
13 | margin: 0 0 var(--shared-margin) 0;
14 | }
15 |
16 | .css-properties-partial {
17 | p + p {
18 | margin-top: var(--shared-margin);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'url';
2 | const componentsDir = fileURLToPath(new URL('./components', import.meta.url));
3 |
4 | export default {
5 | content: [`${componentsDir}/**/*.{js,ts,jsx,tsx}`],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | };
11 |
--------------------------------------------------------------------------------
/examples/styling/styling-project/styling-app/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import { default as react } from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/styling/styling-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/styling/styling-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {% block body %}
23 | {% endblock body %}
24 |
25 | {% block footer %}
26 | {% endblock footer %}
27 |
28 |
29 | {# 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 #}
30 | {{ standard_footer_includes }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/styling/styling-theme/styling.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% js_partial
11 | path="@projects/styling-project/styling-app/components/partials/CSSModulesPartial.jsx" %}
12 |
13 | {% js_partial
14 | path="@projects/styling-project/styling-app/components/partials/CSSPropertiesPartial.jsx" %}
15 |
16 | {% js_partial
17 | path="@projects/styling-project/styling-app/components/partials/InlineStylesPartial.jsx" %}
18 |
19 | {% endblock body %}
20 |
--------------------------------------------------------------------------------
/examples/styling/styling-theme/tailwind.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% js_partial
11 | path="@projects/styling-project/styling-app/components/partials/TailwindPartial.jsx" %}
12 |
13 | {% endblock body %}
14 |
--------------------------------------------------------------------------------
/examples/styling/styling-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Syling",
3 | "preview_path": "./cmss-modules.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/todo-mvc/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parserOptions: {
3 | sourceType: 'module',
4 | ecmaFeatures: {
5 | jsx: true,
6 | },
7 | },
8 | env: {
9 | node: true,
10 | es2021: true,
11 | },
12 | extends: ['eslint:recommended', 'prettier', 'plugin:react/recommended'],
13 | rules: {
14 | 'react/react-in-jsx-scope': 'off',
15 | 'react/prop-types': 'off',
16 | },
17 | ignorePatterns: ['hello-world-project/cms-assets/dist/*'],
18 | settings: {
19 | react: {
20 | version: '18.1',
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/examples/todo-mvc/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | hubspot.config.yml
3 | dist
--------------------------------------------------------------------------------
/examples/todo-mvc/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "overrides": [
7 | {
8 | "files": "*.hubl.html",
9 | "options": {
10 | "parser": "hubl"
11 | }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/examples/todo-mvc/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 HubSpot, Inc.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software
9 | distributed under the License is distributed on an "AS IS" BASIS,
10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | See the License for the specific language governing permissions and
12 | limitations under the License.
--------------------------------------------------------------------------------
/examples/todo-mvc/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "NodeNext"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/todo-mvc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-building-blocks-todo-mvc",
3 | "description": "JS Building Blocks Todo MVC",
4 | "license": "Apache-2.0",
5 | "devDependencies": {
6 | "@hubspot/cli": "latest",
7 | "@hubspot/prettier-plugin-hubl": "latest",
8 | "eslint": "^8.24.0",
9 | "eslint-config-prettier": "^8.5.0",
10 | "eslint-plugin-react": "^7.31.10",
11 | "prettier": "^2.7.1",
12 | "yarpm": "^1.2.0"
13 | },
14 | "scripts": {
15 | "start": "cd todo-mvc-project/todo-mvc-app && yarpm start",
16 | "postinstall": "cd todo-mvc-project/todo-mvc-app && yarpm install",
17 | "lint:js": "eslint . --ext .js,.jsx",
18 | "prettier": "prettier . --check",
19 | "watch:hubl": "hs watch todo-mvc-theme todo-mvc-theme",
20 | "upload:hubl": "hs upload todo-mvc-theme todo-mvc-theme",
21 | "deploy": "hs project upload"
22 | },
23 | "dependencies": {},
24 | "engines": {
25 | "node": ">=16.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/.hsignore:
--------------------------------------------------------------------------------
1 | README.md
2 | tsconfig.json
3 | yarn.lock
4 | dist
5 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/hsproject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-mvc-project",
3 | "srcDir": ".",
4 | "platformVersion": "2023.2"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/.node-version:
--------------------------------------------------------------------------------
1 | 16.13.2
2 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/cms-assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Todo MVC",
3 | "outputPath": ""
4 | }
5 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/modules/TodoMVCModule/fields.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ModuleFields, TextField } from '@hubspot/cms-components/fields';
3 |
4 | export const fields = (
5 |
6 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/modules/TodoMVCModule/index.jsx:
--------------------------------------------------------------------------------
1 | import TodoMVC from '../../todomvc-separate-islands/index.jsx';
2 |
3 | const DEFAULT_TODOS_IF_NO_FETCH = [
4 | {
5 | id: 'default-todo-1',
6 | name: 'Default todo 1',
7 | completed: false,
8 | },
9 | {
10 | id: 'default-todo-2',
11 | name: 'Default todo 2',
12 | completed: true,
13 | },
14 | {
15 | id: 'tf-esoteric-nerd-reference', // https://twitter.com/thedungeonrun/status/1508823885948628992
16 | name: 'Human kind, be both 🧡🙏',
17 | completed: false,
18 | }
19 | ];
20 |
21 | export function Component({ todoPlaceholder, dataQueryResult }) {
22 | const todosFromHubDB =
23 | dataQueryResult?.data?.HUBDB?.todo_js_example_collection?.items;
24 |
25 | const initialTodos = todosFromHubDB ?? DEFAULT_TODOS_IF_NO_FETCH;
26 |
27 | return (
28 |
29 | );
30 | }
31 |
32 | export { fields } from './fields.jsx';
33 |
34 | export const meta = {
35 | label: `TodoMVC module`,
36 | };
37 |
38 | export const query = `query AllTodos {
39 | HUBDB {
40 | todo_js_example_collection {
41 | items {
42 | id: hs_id
43 | name
44 | completed
45 | }
46 | }
47 | }
48 | }`;
49 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/app/app.jsx:
--------------------------------------------------------------------------------
1 | import { Header } from '../header/header.jsx';
2 | import List from '../list/list.jsx?island';
3 | import Footer from '../footer/footer.jsx?island';
4 | import { CopyRight } from '../copy-right/copy-right.jsx';
5 | import { Island } from '@hubspot/cms-components';
6 |
7 | export function App({ todoPlaceholder }) {
8 | return (
9 |
10 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/copy-right/copy-right.jsx:
--------------------------------------------------------------------------------
1 | export function CopyRight() {
2 | return (
3 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/footer/footer.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'clsx';
2 | import { FILTERS } from '../../constants/filter.js';
3 | import {
4 | selectCompleted,
5 | selectNotCompleted,
6 | } from '../../store/selectors/todo.js';
7 | import { onClearCompleted } from '../../store/actions/todo.js';
8 | import {
9 | pushHistoryState,
10 | useSharedIslandReducer,
11 | usePageUrl,
12 | } from '@hubspot/cms-components';
13 | import useFilterFromURL from '../hooks/useFilterFromURL.js';
14 |
15 | export default function Footer() {
16 | const pageURL = usePageUrl();
17 | const [todos, dispatch] = useSharedIslandReducer();
18 | const filter = useFilterFromURL();
19 |
20 | const filterTitles = [
21 | { key: FILTERS.all, value: 'All' },
22 | { key: FILTERS.active, value: 'Active' },
23 | { key: FILTERS.completed, value: 'Completed' },
24 | ];
25 | const completedCount = selectCompleted(todos).length;
26 | const itemsLeft = selectNotCompleted(todos).length;
27 | const clearCompleted = () => dispatch(onClearCompleted());
28 |
29 | const filterSelect = (selectedFilter) => {
30 | const newURL = new URL(pageURL);
31 | newURL.searchParams.set('filter', selectedFilter);
32 | pushHistoryState({}, newURL);
33 | };
34 |
35 | const itemText = itemsLeft === 1 ? 'item' : 'items';
36 |
37 | if (todos.length === 0) {
38 | return null;
39 | }
40 |
41 | return (
42 |
43 |
44 | {itemsLeft}
45 | {itemText} left
46 |
47 |
60 | {!!completedCount && (
61 |
62 | Clear completed
63 |
64 | )}
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/header/TodoInput.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { onCreate } from '../../store/actions/todo.js';
3 | import { useSharedIslandReducer } from '@hubspot/cms-components';
4 |
5 | const ENTER_KEY = 'Enter';
6 |
7 | export default function TodoInput({ className, placeholder }) {
8 | const [name, setName] = useState('');
9 | const [, dispatch] = useSharedIslandReducer();
10 |
11 | const handleChange = (event) => setName(event.target.value);
12 |
13 | const handleSubmit = (event) => {
14 | if (event.key !== ENTER_KEY) {
15 | return;
16 | }
17 |
18 | dispatch(onCreate(name));
19 | onCreate(name);
20 | setName('');
21 | };
22 |
23 | return (
24 | {}}
31 | />
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/header/header.jsx:
--------------------------------------------------------------------------------
1 | import TodoInputIsland from './TodoInput.jsx?island';
2 | import { Island } from '@hubspot/cms-components';
3 |
4 | export function Header({ todoPlaceholder }) {
5 | return (
6 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/hooks/useFilterFromURL.js:
--------------------------------------------------------------------------------
1 | import { usePageUrl } from '@hubspot/cms-components';
2 | import { FILTERS } from '../../constants/filter';
3 |
4 | export default function useFilterFromURL() {
5 | const pageURL = usePageUrl();
6 | return pageURL.searchParams.get('filter') || FILTERS.all;
7 | }
8 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/item/item.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'clsx';
4 |
5 | export function Item({ todo, onUpdate, onRemove }) {
6 | const [editing, setEditing] = useState(false);
7 | const [name, setName] = useState(todo.name);
8 |
9 | const handleEdit = () => setEditing(true);
10 | const handleCompleted = () =>
11 | onUpdate({ id: todo.id, completed: !todo.completed });
12 | const handleRemove = () => onRemove(todo.id);
13 | const handleChange = (event) => setName(event.target.value);
14 | const handleBlur = () => {
15 | onUpdate({ id: todo.id, name });
16 | setEditing(false);
17 | };
18 |
19 | const { completed } = todo;
20 |
21 | return (
22 |
23 |
24 |
30 |
31 | {todo.name}
32 |
33 |
38 |
39 | {editing && (
40 | {}}
46 | />
47 | )}
48 |
49 | );
50 | }
51 |
52 | Item.propTypes = {
53 | todo: PropTypes.object.isRequired,
54 | onUpdate: PropTypes.func.isRequired,
55 | onRemove: PropTypes.func.isRequired,
56 | };
57 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/components/list/list.jsx:
--------------------------------------------------------------------------------
1 | import { Item } from '../item/item.jsx';
2 | import { selectVisible } from '../../store/selectors/todo.js';
3 | import { onUpdate, onRemove, onCompleteAll } from '../../store/actions/todo.js';
4 | import useFilterFromURL from '../hooks/useFilterFromURL.js';
5 | import { useSharedIslandReducer } from '@hubspot/cms-components';
6 |
7 | export default function List() {
8 | const [todos, dispatch] = useSharedIslandReducer();
9 | const filter = useFilterFromURL();
10 |
11 | const visibleTodos = selectVisible(todos, filter);
12 | const areAllCompleted = todos.length && todos.every((todo) => todo.completed);
13 | const completeAll = () => dispatch(onCompleteAll());
14 | const update = (values) => dispatch(onUpdate(values));
15 | const remove = (id) => dispatch(onRemove(id));
16 |
17 | if (todos.length === 0) {
18 | return null;
19 | }
20 |
21 | return (
22 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/constants/action-type.js:
--------------------------------------------------------------------------------
1 | export const ACTION_TYPES = {
2 | load: 'LOAD',
3 | create: 'CREATE',
4 | remove: 'REMOVE',
5 | update: 'UPDATE_TODO',
6 | completeAll: 'COMPLETE_ALL',
7 | clearCompleted: 'CLEAR_COMPLETED',
8 | selectFilter: 'SELECT_FILTER'
9 | };
10 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/constants/filter.js:
--------------------------------------------------------------------------------
1 | export const FILTERS = {
2 | all: 'all',
3 | active: 'active',
4 | completed: 'completed'
5 | };
6 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/index.jsx:
--------------------------------------------------------------------------------
1 | import { SharedIslandReducer } from '@hubspot/cms-components';
2 | import 'todomvc-app-css/index.css';
3 | import { App } from './components/app/app.jsx';
4 | import todoReducer from './store/reducers/todo.js?client';
5 |
6 | export default function TodoMVCReactHooks({
7 | initialTodos = [],
8 | todoPlaceholder,
9 | }) {
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/store/actions/todo.js:
--------------------------------------------------------------------------------
1 | import { ACTION_TYPES } from '../../constants/action-type.js';
2 |
3 | export const onLoad = (todos) => ({ type: ACTION_TYPES.load, todos });
4 | export const onCreate = (name) => ({ type: ACTION_TYPES.create, name });
5 | export const onRemove = (id) => ({ type: ACTION_TYPES.remove, id });
6 | export const onUpdate = (values) => ({ type: ACTION_TYPES.update, values });
7 | export const onCompleteAll = () => ({ type: ACTION_TYPES.completeAll });
8 | export const onClearCompleted = () => ({ type: ACTION_TYPES.clearCompleted });
9 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/store/reducers/todo.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from 'uuid';
2 | import { ACTION_TYPES } from '../../constants/action-type.js';
3 | import { selectCompleted, selectNotCompleted } from '../selectors/todo.js';
4 |
5 | const areAllCompleted = (state) =>
6 | state.length && selectCompleted(state).length === state.length;
7 |
8 | export default function todosReducer(state = [], action) {
9 | switch (action.type) {
10 | case ACTION_TYPES.load:
11 | return [...action.todos];
12 | case ACTION_TYPES.create:
13 | return [...state, { id: uuidv4(), name: action.name, completed: false }];
14 | case ACTION_TYPES.update:
15 | return state.map((todo) =>
16 | todo.id === action.values.id ? { ...todo, ...action.values } : todo,
17 | );
18 | case ACTION_TYPES.remove:
19 | return state.filter((todo) => todo.id !== action.id);
20 | case ACTION_TYPES.completeAll:
21 | return state.map((todo) => ({
22 | ...todo,
23 | ...{ completed: !areAllCompleted(state) },
24 | }));
25 | case ACTION_TYPES.clearCompleted:
26 | return selectNotCompleted(state);
27 | default:
28 | return state;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/components/todomvc-separate-islands/store/selectors/todo.js:
--------------------------------------------------------------------------------
1 | import { FILTERS } from '../../constants/filter.js';
2 |
3 | export function selectCompleted(todos) {
4 | return todos.filter((todo) => todo.completed);
5 | }
6 |
7 | export function selectNotCompleted(todos) {
8 | return todos.filter((todo) => !todo.completed);
9 | }
10 |
11 | export function selectVisible(todos, filter) {
12 | switch (filter) {
13 | case FILTERS.all:
14 | return [...todos];
15 | case FILTERS.completed:
16 | return selectCompleted(todos);
17 | case FILTERS.active:
18 | return selectNotCompleted(todos);
19 | default:
20 | return [...todos];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-mvc-app",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "dependencies": {
6 | "@hubspot/cms-components": "latest",
7 | "clsx": "^1.2.1",
8 | "prop-types": "^15.8.1",
9 | "react": "^18.1.0",
10 | "todomvc-app-css": "^2.4.2",
11 | "uuid": "^9.0.0"
12 | },
13 | "devDependencies": {
14 | "@hubspot/cms-dev-server": "latest",
15 | "@testing-library/react": "^13.4.0",
16 | "@vitejs/plugin-react": "^2.1.0",
17 | "jsdom": "^20.0.1",
18 | "vitest": "^0.24.3"
19 | },
20 | "scripts": {
21 | "start": "hs-cms-dev-server .",
22 | "test": "vitest"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-project/todo-mvc-app/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 | import { default as react } from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | test: {
7 | environment: 'jsdom',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-theme/fields.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "Color",
4 | "name": "brand_color",
5 | "label": "Brand Color",
6 | "default": {
7 | "color": "#FF7A59",
8 | "opacity": 100
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-theme/layouts/base.hubl.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 | {% if page_meta.html_title or pageTitle %}
9 | {{ page_meta.html_title or pageTitle }}
10 | {% endif %}
11 | {% if brand_settings.primaryFavicon.src %}
12 |
13 | {% endif %}
14 |
15 | {{ standard_header_includes }}
16 |
17 |
18 |
19 | {% block header %}
20 | {% endblock header %}
21 |
22 | {% block body %}
23 | {% endblock body %}
24 |
25 | {% block footer %}
26 | {% endblock footer %}
27 |
28 |
29 | {# 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 #}
30 | {{ standard_footer_includes }}
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-theme/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "JS Building Blocks - Todo MVC",
3 | "preview_path": "./todo-mvc.hubl.html"
4 | }
5 |
--------------------------------------------------------------------------------
/examples/todo-mvc/todo-mvc-theme/todo-mvc.hubl.html:
--------------------------------------------------------------------------------
1 |
6 | {% extends "./layouts/base.hubl.html" %}
7 |
8 | {% block body %}
9 |
10 | {% module "TodoMVC"
11 | path="@projects/todo-mvc-project/todo-mvc-app/components/modules/TodoMVCModule" %}
12 |
13 | {% endblock body %}
14 |
--------------------------------------------------------------------------------