├── .babelrc.json ├── .eslintrc.json ├── .github ├── images │ └── helium_logo.png └── workflows │ ├── jest.yml │ ├── publish.yml │ └── storybook.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierrc ├── .storybook ├── i18next.tsx ├── main.ts ├── manager-head.html ├── manager.ts ├── preview.js ├── public │ └── favicon.ico ├── style.css └── theme.ts ├── .vscode ├── launch.json └── settings.json ├── LICENSE.md ├── README.md ├── docs ├── .gitignore ├── 0.6421a385493e560031e3.manager.bundle.js ├── 0.6421a385493e560031e3.manager.bundle.js.LICENSE.txt ├── 0.98c85db3.iframe.bundle.js ├── 0.98c85db3.iframe.bundle.js.LICENSE.txt ├── 0.98c85db3.iframe.bundle.js.map ├── 1.2b3f6588e03eece45d23.manager.bundle.js ├── 1.58138a0b.iframe.bundle.js ├── 1.58138a0b.iframe.bundle.js.LICENSE.txt ├── 1.58138a0b.iframe.bundle.js.map ├── 2.af7b2c8a.iframe.bundle.js ├── 3.8bb2173c.iframe.bundle.js ├── 5.f9625236d18921051070.manager.bundle.js ├── 6.c58a110bdffea85e893a.manager.bundle.js ├── 6.c58a110bdffea85e893a.manager.bundle.js.LICENSE.txt ├── 7.388fec314db3a385111e.manager.bundle.js ├── 7.8080c7cb.iframe.bundle.js ├── 8.91de97de.iframe.bundle.js ├── 8.91de97de.iframe.bundle.js.LICENSE.txt ├── 8.91de97de.iframe.bundle.js.map ├── 8.9f9de3431b054586b2f2.manager.bundle.js ├── 9.40ec65d6.iframe.bundle.js ├── favicon.ico ├── iframe.html ├── index.html ├── main.2ff507830b5d7d60b7ae.manager.bundle.js ├── main.d9417078.iframe.bundle.js ├── runtime~main.7f7c32ef.iframe.bundle.js ├── runtime~main.ee7e221e1f4890e691e1.manager.bundle.js ├── vendors~main.205eebd34c45884edb09.manager.bundle.js ├── vendors~main.205eebd34c45884edb09.manager.bundle.js.LICENSE.txt ├── vendors~main.530a53d0.iframe.bundle.js ├── vendors~main.530a53d0.iframe.bundle.js.LICENSE.txt └── vendors~main.530a53d0.iframe.bundle.js.map ├── i18n ├── en.json └── i18n.ts ├── jest.config.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── cart │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── cart.test.tsx │ │ └── core │ │ │ └── components │ │ │ └── cart-provider │ │ │ ├── reducer.test.ts │ │ │ └── utilities.test.ts │ ├── codegen.yml │ ├── package.json │ ├── src │ │ ├── cart-button.tsx │ │ ├── cart-modal.tsx │ │ ├── cart.tsx │ │ ├── constants.ts │ │ ├── core │ │ │ ├── components │ │ │ │ ├── add-to-cart-button │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── cart-checkout-button │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── cart-provider │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── provider.tsx │ │ │ │ │ ├── reducer.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── use-persist-reducer.ts │ │ │ │ │ └── utilities.ts │ │ │ │ ├── cart-ui-provider │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── provider.tsx │ │ │ │ │ └── types.ts │ │ │ │ └── index.ts │ │ │ ├── graphql │ │ │ │ ├── global-types.ts │ │ │ │ ├── index.ts │ │ │ │ └── queries │ │ │ │ │ ├── RelatedProducts.generated.tsx │ │ │ │ │ ├── RelatedProducts.graphql │ │ │ │ │ └── index.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── use-cart-checkout │ │ │ │ │ ├── hook.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utilities.ts │ │ │ │ ├── use-cart-ui │ │ │ │ │ └── index.ts │ │ │ │ └── use-cart │ │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ └── types.ts │ └── stories │ │ └── Cart.stories.tsx ├── catalog │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── catalog.test.tsx │ │ └── core │ │ │ └── utilities │ │ │ └── manage-catalog-url │ │ │ └── catalog-url-manager.test.ts │ ├── package.json │ ├── src │ │ ├── catalog-aggregations.tsx │ │ ├── catalog-error.tsx │ │ ├── catalog-filters.tsx │ │ ├── catalog-loader.tsx │ │ ├── catalog-pagination.tsx │ │ ├── catalog-results.tsx │ │ ├── catalog.tsx │ │ ├── constants.ts │ │ ├── core │ │ │ ├── components │ │ │ │ ├── catalog-link-button │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── link.tsx │ │ │ │ │ └── types.ts │ │ │ │ ├── catalog-provider │ │ │ │ │ ├── client-provider.tsx │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── provider.tsx │ │ │ │ │ ├── server-provider.tsx │ │ │ │ │ └── types.ts │ │ │ │ ├── height-equalizer │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── height-equalizer-element.tsx │ │ │ │ │ ├── height-equalizer.tsx │ │ │ │ │ ├── hook.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ └── index.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── use-catalog │ │ │ │ │ ├── index.ts │ │ │ │ │ └── use-catalog.ts │ │ │ ├── index.ts │ │ │ └── utilities │ │ │ │ ├── index.ts │ │ │ │ ├── manage-catalog-url │ │ │ │ ├── catalog-url-manager.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utilities.ts │ │ │ │ └── parse-catalog-data │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── parse-aggregation-filters.ts │ │ │ │ ├── parse-query-variables.ts │ │ │ │ ├── parse-response-data.ts │ │ │ │ ├── parse-sort.ts │ │ │ │ ├── serialize-sort.ts │ │ │ │ └── types.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── types.ts │ │ └── variants │ │ │ ├── display-type-icon │ │ │ ├── calendar.tsx │ │ │ ├── grid.tsx │ │ │ ├── index.ts │ │ │ ├── link.tsx │ │ │ └── list.tsx │ │ │ ├── display-type-results │ │ │ ├── calendar.tsx │ │ │ ├── grid.tsx │ │ │ ├── icons.tsx │ │ │ ├── index.ts │ │ │ ├── item-asset-block.tsx │ │ │ ├── item-link-wrapper.tsx │ │ │ ├── item-queue-button.tsx │ │ │ ├── item-ribbon.tsx │ │ │ ├── list.tsx │ │ │ └── utilities.ts │ │ │ ├── filter-selector │ │ │ ├── constants.ts │ │ │ ├── content-type-selector.tsx │ │ │ ├── display-type-selector.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── index.ts │ │ │ ├── search-input.tsx │ │ │ └── sort-selector.tsx │ │ │ └── filter │ │ │ ├── aggregation.tsx │ │ │ ├── content-type.tsx │ │ │ ├── index.ts │ │ │ ├── link.tsx │ │ │ ├── remove-icon.tsx │ │ │ ├── search-term.tsx │ │ │ ├── token-label.tsx │ │ │ └── wrapper.tsx │ └── stories │ │ └── Catalog.stories.tsx ├── contact-block │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── contact-block.test.tsx │ ├── package.json │ ├── src │ │ ├── contact-block.tsx │ │ ├── index.ts │ │ └── types.ts │ └── stories │ │ └── contact-block.stories.tsx ├── content │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── components │ │ │ └── loading-dots │ │ │ │ └── loading-dots.test.tsx │ │ └── utilities │ │ │ ├── format-time │ │ │ └── format-time.test.ts │ │ │ └── hydrate-content │ │ │ ├── course-run.test.ts │ │ │ └── hydrate-content.test.ts │ ├── codegen.yml │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── content-header │ │ │ │ ├── content-header.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ └── loading-dots │ │ │ │ ├── index.ts │ │ │ │ └── loading-dots.tsx │ │ ├── graphql │ │ │ ├── global-types.ts │ │ │ ├── index.ts │ │ │ ├── mutations │ │ │ │ ├── AddResourceToQueue.generated.tsx │ │ │ │ ├── AddResourceToQueue.graphql │ │ │ │ ├── ArchiveUserCourse.generated.tsx │ │ │ │ ├── ArchiveUserCourse.graphql │ │ │ │ ├── ArchiveUserLearningPath.generated.tsx │ │ │ │ ├── ArchiveUserLearningPath.graphql │ │ │ │ ├── CreateCertificateFromUpload.generated.tsx │ │ │ │ ├── CreateCertificateFromUpload.graphql │ │ │ │ ├── DestroyBookmark.generated.tsx │ │ │ │ ├── DestroyBookmark.graphql │ │ │ │ ├── DestroyBookmarkFolder.generated.tsx │ │ │ │ ├── DestroyBookmarkFolder.graphql │ │ │ │ ├── ReinstateUserCourse.generated.tsx │ │ │ │ ├── ReinstateUserCourse.graphql │ │ │ │ ├── ReinstateUserLearningPath.generated.tsx │ │ │ │ ├── ReinstateUserLearningPath.graphql │ │ │ │ ├── UnenrollFromWaitlist.generated.tsx │ │ │ │ ├── UnenrollFromWaitlist.graphql │ │ │ │ ├── UpdateBookmark.generated.tsx │ │ │ │ ├── UpdateBookmark.graphql │ │ │ │ ├── UpdateBookmarkFolder.generated.tsx │ │ │ │ ├── UpdateBookmarkFolder.graphql │ │ │ │ └── index.ts │ │ │ └── queries │ │ │ │ ├── CatalogContent.generated.tsx │ │ │ │ ├── CatalogContent.graphql │ │ │ │ ├── CatalogMetaFragment.generated.tsx │ │ │ │ ├── CatalogMetaFragment.graphql │ │ │ │ ├── CatalogQuery.generated.tsx │ │ │ │ ├── CatalogQuery.graphql │ │ │ │ ├── ContentFragment.generated.tsx │ │ │ │ ├── ContentFragment.graphql │ │ │ │ ├── CourseGroupBySlug.generated.tsx │ │ │ │ ├── CourseGroupBySlug.graphql │ │ │ │ ├── Languages.generated.tsx │ │ │ │ ├── Languages.graphql │ │ │ │ ├── LearningPathBySlug.generated.tsx │ │ │ │ ├── LearningPathBySlug.graphql │ │ │ │ ├── LocationFragment.generated.tsx │ │ │ │ ├── LocationFragment.graphql │ │ │ │ ├── QueryContents.generated.tsx │ │ │ │ ├── QueryContents.graphql │ │ │ │ ├── RssItems.generated.tsx │ │ │ │ ├── RssItems.graphql │ │ │ │ ├── UserArchives.generated.tsx │ │ │ │ ├── UserArchives.graphql │ │ │ │ ├── UserBookmarks.generated.tsx │ │ │ │ ├── UserBookmarks.graphql │ │ │ │ ├── UserBookmarksByFolder.generated.tsx │ │ │ │ ├── UserBookmarksByFolder.graphql │ │ │ │ ├── UserCertificateFields.generated.tsx │ │ │ │ ├── UserCertificateFields.graphql │ │ │ │ ├── UserCertificates.generated.tsx │ │ │ │ ├── UserCertificates.graphql │ │ │ │ ├── UserContentGroups.generated.tsx │ │ │ │ ├── UserContentGroups.graphql │ │ │ │ ├── UserContentItems.generated.tsx │ │ │ │ ├── UserContentItems.graphql │ │ │ │ ├── UserCourseAwardCounts.generated.tsx │ │ │ │ ├── UserCourseAwardCounts.graphql │ │ │ │ ├── UserCourseCollaborations.generated.tsx │ │ │ │ ├── UserCourseCollaborations.graphql │ │ │ │ ├── UserCourseCompletionProgress.generated.tsx │ │ │ │ ├── UserCourseCompletionProgress.graphql │ │ │ │ ├── UserCourseProgress.generated.tsx │ │ │ │ ├── UserCourseProgress.graphql │ │ │ │ ├── UserRecentContent.generated.tsx │ │ │ │ ├── UserRecentContent.graphql │ │ │ │ ├── UserWaitlist.generated.tsx │ │ │ │ ├── UserWaitlist.graphql │ │ │ │ └── index.ts │ │ ├── index.ts │ │ └── utilities │ │ │ ├── format-time │ │ │ ├── constants.ts │ │ │ ├── format-time.ts │ │ │ └── index.ts │ │ │ ├── hydrate-content │ │ │ ├── course-run.ts │ │ │ ├── hydrate-content.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ └── index.ts │ └── stories │ │ └── content-header.stories.tsx ├── dashboard-stats │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── dashboard-stats.test.tsx │ ├── codegen.yml │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── dashboard-stats.tsx │ │ │ ├── icons.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── graphql │ │ │ ├── UserStats.generated.tsx │ │ │ ├── UserStats.graphql │ │ │ ├── global-types.ts │ │ │ └── index.ts │ │ └── index.ts │ └── stories │ │ └── DashboardStats.stories.tsx ├── featured-content │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── featured-content.test.tsx │ ├── package.json │ ├── src │ │ ├── featured-content.tsx │ │ ├── index.ts │ │ ├── types.ts │ │ └── variants │ │ │ ├── content │ │ │ ├── carousel.tsx │ │ │ ├── icons.tsx │ │ │ ├── index.ts │ │ │ ├── item-asset-block.tsx │ │ │ ├── item-completed-block.tsx │ │ │ ├── item-link-wrapper.tsx │ │ │ ├── item-queue-button.tsx │ │ │ ├── multi-carousel.tsx │ │ │ ├── tile-descriptive-layout.tsx │ │ │ ├── tile-image-overlay.tsx │ │ │ ├── tile-standard-layout.tsx │ │ │ ├── utils.ts │ │ │ └── wrapper.tsx │ │ │ └── sidebar │ │ │ ├── default.tsx │ │ │ ├── index.ts │ │ │ ├── rss.tsx │ │ │ └── wrapper.tsx │ └── stories │ │ ├── FeaturedContent.stories.tsx │ │ └── FeaturedContentWithDataFetching.stories.tsx ├── header │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── header.test.tsx │ ├── package.json │ ├── src │ │ ├── header.tsx │ │ ├── index.ts │ │ └── types.ts │ └── stories │ │ └── Header.stories.tsx ├── hero │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── hero.test.tsx │ ├── package.json │ ├── src │ │ ├── hero.tsx │ │ ├── index.ts │ │ └── types.ts │ └── stories │ │ └── Hero.stories.tsx ├── hooks │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.ts │ │ ├── use-carousel-behavior.ts │ │ ├── use-media.ts │ │ ├── use-multi-carousel-behavior.ts │ │ ├── use-on-click-outside.ts │ │ ├── use-previous.ts │ │ ├── use-screen-size.ts │ │ ├── use-sdk.ts │ │ └── use-window-event-listener.ts ├── link-lists │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── link-lists.test.tsx │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── link-list.tsx │ │ ├── link-lists.tsx │ │ └── types.ts │ └── stories │ │ └── LinkLists.stories.tsx ├── navigation-bar │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── navigation-bar.test.tsx │ ├── package.json │ ├── src │ │ ├── context.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── navigation-bar-link.tsx │ │ ├── navigation-bar.tsx │ │ ├── types.ts │ │ ├── use-mobile-topbar-behavior.ts │ │ └── use-navigation-bar.ts │ └── stories │ │ └── NavigationBar.stories.tsx ├── pagination │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── pagination.test.tsx │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── pagination.tsx │ │ ├── types.ts │ │ └── utilities.ts │ └── stories │ │ └── Pagination.stories.tsx ├── testimonial │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ └── testimonial.test.tsx │ ├── package.json │ ├── src │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── multi-carousel.tsx │ │ ├── types.ts │ │ └── utils.ts │ └── stories │ │ └── Testimonial.stories.tsx ├── user │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── login.test.tsx │ │ └── registration.test.tsx │ ├── codegen.yml │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── banner │ │ │ │ ├── banner.tsx │ │ │ │ └── index.ts │ │ │ ├── icons.tsx │ │ │ ├── index.ts │ │ │ ├── login │ │ │ │ ├── index.tsx │ │ │ │ └── login.tsx │ │ │ ├── redemption │ │ │ │ ├── code-box.tsx │ │ │ │ ├── index.ts │ │ │ │ └── redemption.tsx │ │ │ ├── registration │ │ │ │ ├── index.ts │ │ │ │ └── registration.tsx │ │ │ ├── terms-conditions-modal │ │ │ │ ├── index.ts │ │ │ │ ├── terms-conditions-modal.tsx │ │ │ │ └── terms.tsx │ │ │ ├── terms-conditions │ │ │ │ ├── index.ts │ │ │ │ └── terms-conditions.tsx │ │ │ └── types.ts │ │ ├── graphql │ │ │ ├── global-types.ts │ │ │ ├── index.ts │ │ │ ├── mutations │ │ │ │ ├── Login.generated.tsx │ │ │ │ ├── Login.graphql │ │ │ │ ├── RedeemRegistrationAndRedemptionCodes.generated.tsx │ │ │ │ ├── RedeemRegistrationAndRedemptionCodes.graphql │ │ │ │ ├── ValidateRedemptionCode.generated.tsx │ │ │ │ ├── ValidateRedemptionCode.graphql │ │ │ │ └── index.ts │ │ │ └── queries │ │ │ │ ├── TermsAndConditions.generated.tsx │ │ │ │ ├── TermsAndConditions.graphql │ │ │ │ └── index.ts │ │ └── index.ts │ └── stories │ │ ├── Login.stories.tsx │ │ └── Registration.stories.tsx └── video-player │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ └── video-player.test.tsx │ ├── package.json │ ├── src │ ├── index.ts │ ├── types.ts │ └── video-player.tsx │ └── stories │ └── VideoPlayer.stories.tsx ├── postcss.config.js ├── tailwind.config.js ├── tooling ├── cli │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── graphql.test.js │ │ └── test-query-files │ │ │ ├── ContentFragment.js │ │ │ ├── QueryNoTypename.js │ │ │ └── QueryWithFragment.js │ ├── lib │ │ ├── command-modules │ │ │ ├── authenticate.js │ │ │ ├── deploy.js │ │ │ ├── dev.js │ │ │ └── update-translations.js │ │ ├── deploy.js │ │ ├── deployment-log.js │ │ ├── file-generators.js │ │ ├── helpers │ │ │ ├── atoms.js │ │ │ ├── constants.js │ │ │ ├── filepaths.js │ │ │ ├── graphql.js │ │ │ ├── prompts.js │ │ │ ├── translations.js │ │ │ └── urls.js │ │ ├── index.js │ │ ├── parse-translations.js │ │ ├── prompts.js │ │ ├── update-translations.js │ │ └── validations.js │ └── package.json ├── create-helium-app │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── index.js │ ├── package.json │ ├── scripts │ │ ├── tmp-copy-template-from-tooling.js │ │ └── utils.js │ ├── template-base │ └── template-base-essentials ├── graphql-codegen-plugin │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ └── src │ │ ├── config.ts │ │ ├── index.ts │ │ └── visitor.ts ├── helium-server │ ├── .gitignore │ ├── CHANGELOG.md │ ├── package.json │ ├── src │ │ ├── clients │ │ │ └── graphiql │ │ │ │ ├── index.css │ │ │ │ ├── index.html │ │ │ │ └── main.tsx │ │ ├── index.ts │ │ ├── server │ │ │ └── server.ts │ │ ├── utilities │ │ │ ├── fetch-user-and-appearance.ts │ │ │ ├── find-ti-instance.ts │ │ │ ├── init-page-context.ts │ │ │ └── make-apollo-client.ts │ │ └── vite-config │ │ │ └── vite.config.ts │ ├── vite.graphiql.config.ts │ └── worker │ │ ├── index.js │ │ ├── init-page-context.js │ │ ├── ssr.js │ │ └── static-assets.js ├── storybook-addon-apollo-client │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── preset.js │ ├── src │ │ ├── constants.ts │ │ ├── decorators.tsx │ │ ├── index.tsx │ │ ├── panel.tsx │ │ ├── preset │ │ │ ├── addDecorator.ts │ │ │ └── index.ts │ │ ├── register.tsx │ │ ├── title.ts │ │ └── types.ts │ └── tsconfig.json ├── tailwind-preset │ ├── .gitignore │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ └── index.ts ├── template-base-essentials │ ├── .env.example │ ├── .gitignore │ ├── .npmrc │ ├── .stackblitzrc │ ├── CHANGELOG.md │ ├── README.md │ ├── __npmrc │ ├── _gitignore │ ├── components │ │ ├── CommunityNav.tsx │ │ ├── HomepageHeader.tsx │ │ ├── RenderLinks.tsx │ │ └── TitleAndBody.tsx │ ├── env.d.ts │ ├── locales │ │ └── .gitignore │ ├── package.json │ ├── pages │ │ └── index │ │ │ └── index.page.tsx │ ├── renderer │ │ ├── Link.jsx │ │ ├── PageWrapper.css │ │ ├── PageWrapper.tsx │ │ ├── _default.page.client.tsx │ │ ├── _default.page.server.tsx │ │ ├── _error.page.tsx │ │ ├── discord.svg │ │ ├── getPageMeta.ts │ │ ├── github.svg │ │ ├── i18n.ts │ │ ├── tailwind.css │ │ ├── usePageContext.tsx │ │ └── wavyHand.svg │ ├── server │ │ └── index.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ └── vite.config.ts └── template-base │ ├── .env.example │ ├── .gitignore │ ├── .npmrc │ ├── .stackblitzrc │ ├── CHANGELOG.md │ ├── README.md │ ├── __npmrc │ ├── _gitignore │ ├── components │ ├── Assets │ │ ├── CtaAsset.tsx │ │ ├── DropDownClosed.tsx │ │ ├── DropDownOpen.tsx │ │ ├── GridSelector.tsx │ │ ├── Hamburger.tsx │ │ ├── HeroImage.tsx │ │ ├── ListViewSelector.tsx │ │ ├── Share.tsx │ │ ├── Xicon.tsx │ │ └── logoImage.tsx │ ├── Avatar │ │ └── Avatar.tsx │ ├── Banner.tsx │ ├── CTA │ │ ├── ButtonLink.tsx │ │ ├── CTA.tsx │ │ ├── CallToActionParagraphs.tsx │ │ └── CallToActionWithLinks.tsx │ ├── CatalogAndAggreg │ │ ├── Aggregations.tsx │ │ ├── CatalogAndAggregation.tsx │ │ ├── CatalogElementsGrid.tsx │ │ ├── CatalogError.tsx │ │ ├── CatalogResults.tsx │ │ └── catalog-item-asset-block.tsx │ ├── CourseLayout.tsx │ ├── CourseSidebar.tsx │ ├── FeaturedContent │ │ └── FeaturedContentComp.tsx │ ├── Footer │ │ └── Footer.tsx │ ├── Hero │ │ └── Hero.tsx │ ├── LearnerAccess │ │ ├── Assets │ │ │ ├── Icons.tsx │ │ │ └── Tooltips.tsx │ │ ├── Context │ │ │ ├── context.ts │ │ │ └── use-context.ts │ │ ├── LearnerAccess.tsx │ │ ├── LoadArchivedContent.tsx │ │ ├── LoadBookmarks.tsx │ │ ├── LoadCertificates.tsx │ │ ├── LoadUserLearning.tsx │ │ ├── LoadWaitlistedContent.tsx │ │ ├── MutationCallingButtons.tsx │ │ ├── Types │ │ │ └── types.ts │ │ ├── Utilities │ │ │ └── utilities.ts │ │ └── Views │ │ │ ├── DisplayListView.tsx │ │ │ ├── GridView.tsx │ │ │ └── ListDisplayDropDown.tsx │ ├── Logo │ │ └── Logo.tsx │ ├── Navigation │ │ ├── CurrentUserNavBar.tsx │ │ ├── CurrentUserSmallScreenNavBar.tsx │ │ ├── NavBar.tsx │ │ └── UserLoginNavBar.tsx │ └── Signin │ │ └── SigninPage.tsx │ ├── env.d.ts │ ├── locales │ └── .gitignore │ ├── package.json │ ├── pages │ ├── catalog │ │ └── index.page.tsx │ ├── index │ │ └── index.page.tsx │ └── learn │ │ ├── dashboard.page.tsx │ │ └── sign_in.page.tsx │ ├── renderer │ ├── HeroImage.svg │ ├── Link.jsx │ ├── PageWrapper.css │ ├── PageWrapper.tsx │ ├── _default.page.client.tsx │ ├── _default.page.server.tsx │ ├── _error.page.tsx │ ├── dropDownClosed.svg │ ├── dropDownOpen.svg │ ├── getPageMeta.ts │ ├── gridSelected.svg │ ├── hamburger.svg │ ├── i18n.ts │ ├── listViewSelector.svg │ ├── logo.svg │ ├── share.svg │ ├── tailwind.css │ ├── trees.png │ ├── usePageContext.tsx │ └── xicon.svg │ ├── server │ └── index.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ └── vite.config.ts ├── tsconfig.json └── tsconfig.stories.json /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100 9 | } 10 | } 11 | ], 12 | "@babel/preset-typescript", 13 | "@babel/preset-react" 14 | ], 15 | "plugins": [] 16 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react/recommended", 11 | "plugin:react-hooks/recommended", 12 | "plugin:@typescript-eslint/recommended", 13 | "plugin:prettier/recommended", 14 | "plugin:storybook/recommended" 15 | ], 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaFeatures": { 19 | "jsx": true 20 | }, 21 | "ecmaVersion": 12, 22 | "sourceType": "module" 23 | }, 24 | "plugins": [ 25 | "react", 26 | "@typescript-eslint" 27 | ], 28 | "rules": { 29 | "react/jsx-curly-brace-presence": [ 30 | "error", 31 | { 32 | "props": "never" 33 | } 34 | ], 35 | "@typescript-eslint/no-var-requires": 0, 36 | "@typescript-eslint/no-explicit-any": [ 37 | "off" 38 | ], 39 | "@typescript-eslint/ban-ts-comment": [ 40 | "error", 41 | { 42 | "ts-ignore": "allow-with-description" 43 | } 44 | ] 45 | }, 46 | "settings": { 47 | "react": { 48 | "version": "detect" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/images/helium_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtindustries/helium/8d96e736d46edda55b4109c8c96ce6a596a56619/.github/images/helium_logo.png -------------------------------------------------------------------------------- /.github/workflows/jest.yml: -------------------------------------------------------------------------------- 1 | name: Run Test Suite 2 | 3 | on: 4 | push: 5 | branches: [staging] 6 | pull_request: 7 | branches: [staging] 8 | 9 | jobs: 10 | build: 11 | env: 12 | NODE_ENV: test 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2.3.1 19 | with: 20 | fetch-depth: 0 21 | persist-credentials: false 22 | 23 | - name: Install depdencies 24 | run: npm ci 25 | 26 | - name: Run Test Suite 27 | run: npm run test -------------------------------------------------------------------------------- /.github/workflows/storybook.yml: -------------------------------------------------------------------------------- 1 | name: Publish Storybook Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - staging 7 | paths: ['packages/**/stories/**', 'packages/**/src/**'] 8 | 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2.3.1 15 | with: 16 | persist-credentials: false 17 | 18 | - name: Install and Build Storybook 19 | run: | 20 | npm ci 21 | npm run build-storybook-ci 22 | touch ./docs-build/.nojekyll 23 | 24 | - name: Deploy 25 | uses: JamesIves/github-pages-deploy-action@3.6.2 26 | with: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | BRANCH: storybook-pages # The branch the action should deploy to. 29 | FOLDER: docs-build # The folder that the build-storybook script generates files. 30 | CLEAN: true # Automatically remove deleted files from the deploy branch 31 | TARGET_FOLDER: docs # The folder that we serve our Storybook files from 32 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | if ! head -1 "$1" | grep -qE "^(feat|fix|ci|chore|docs|test|style|refactor)(\(.+?\))?: .{1,}$"; then 5 | echo "Your commit message does not follow Conventional Commits formatting 6 | https://www.conventionalcommits.org/ 7 | 8 | Conventional Commits start with one of the below types, followed by a colon, 9 | followed by the commit message: 10 | 11 | feat fix ci chore docs test style refactor 12 | 13 | Example commit message adding a feature: 14 | 15 | feat: implement new API 16 | 17 | Example commit message fixing an issue: 18 | 19 | fix: remove infinite loop 20 | 21 | Optionally, include a scope in parentheses after the type for more context: 22 | 23 | fix(account): remove infinite loop" >&2 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /.storybook/i18next.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useEffect } from 'react'; 2 | import { I18nextProvider } from 'react-i18next'; 3 | import { i18n } from '../i18n/i18n'; 4 | 5 | const withI18next = (Story, context) => { 6 | const { locale } = context.globals; 7 | 8 | useEffect(() => { 9 | i18n.changeLanguage(locale); 10 | }, [locale]); 11 | return ( 12 | loading translations...}> 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default withI18next; 21 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/types'; 2 | const config: StorybookConfig = { 3 | stories: ['../packages/'], 4 | staticDirs: ['./public'], 5 | addons: [ 6 | '@storybook/addon-links', 7 | '@storybook/addon-controls', 8 | '@thoughtindustries/storybook-addon-apollo-client', 9 | '@storybook/addon-toolbars', 10 | '@storybook/addon-storysource' 11 | ], 12 | framework: '@storybook/react-vite', 13 | docs: { 14 | autodocs: true 15 | } 16 | }; 17 | export default config; 18 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.storybook/manager.ts: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/manager-api'; 2 | import theme from './theme'; 3 | 4 | addons.setConfig({ 5 | theme: theme 6 | }); 7 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | import { MockedProvider } from '@apollo/client/testing'; 3 | import { cookieDecorator } from 'storybook-addon-cookie'; 4 | import withI18next from './i18next'; 5 | import { i18n } from '../i18n/i18n'; 6 | 7 | export const parameters = { 8 | actions: { argTypesRegex: '^on[A-Z].*' }, 9 | controls: { 10 | matchers: { 11 | color: /(background|color)$/i, 12 | date: /Date$/ 13 | } 14 | }, 15 | apolloClient: { 16 | MockedProvider 17 | }, 18 | i18n, 19 | locale: 'en' 20 | }; 21 | 22 | export const globalTypes = { 23 | locale: { 24 | name: 'Locale', 25 | description: 'Internationalization locale', 26 | defaultValue: 'en', 27 | toolbar: { 28 | icon: 'globe', 29 | items: [{ value: 'en', right: '🇺🇸', title: 'English' }] 30 | } 31 | } 32 | }; 33 | 34 | export const decorators = [cookieDecorator, withI18next]; 35 | -------------------------------------------------------------------------------- /.storybook/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtindustries/helium/8d96e736d46edda55b4109c8c96ce6a596a56619/.storybook/public/favicon.ico -------------------------------------------------------------------------------- /.storybook/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Sintony:300,400,700|Nunito:300,400,600,700"); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; -------------------------------------------------------------------------------- /.storybook/theme.ts: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export default create({ 4 | base: 'light', 5 | brandTitle: 'Thought Industries', 6 | brandUrl: 'https://developer.thoughtindustries.com/', 7 | brandTarget: '_self' 8 | }); 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeArgs": [ 9 | "--inspect-brk", 10 | "${workspaceRoot}/node_modules/.bin/jest", 11 | "--runInBand" 12 | ], 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen", 15 | "port": 9229 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2021 Thought Industries, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtindustries/helium/8d96e736d46edda55b4109c8c96ce6a596a56619/docs/.gitignore -------------------------------------------------------------------------------- /docs/0.6421a385493e560031e3.manager.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * 4 | * @license MIT 5 | * @author Lea Verou 6 | * @namespace 7 | * @public 8 | */ 9 | -------------------------------------------------------------------------------- /docs/0.98c85db3.iframe.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** @license React v16.13.1 2 | * react-is.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | -------------------------------------------------------------------------------- /docs/0.98c85db3.iframe.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"0.98c85db3.iframe.bundle.js","sources":[],"mappings":";A","sourceRoot":""} -------------------------------------------------------------------------------- /docs/1.58138a0b.iframe.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * 4 | * @license MIT 5 | * @author Lea Verou 6 | * @namespace 7 | * @public 8 | */ 9 | -------------------------------------------------------------------------------- /docs/1.58138a0b.iframe.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"1.58138a0b.iframe.bundle.js","sources":[],"mappings":";A","sourceRoot":""} -------------------------------------------------------------------------------- /docs/6.c58a110bdffea85e893a.manager.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * OverlayScrollbars 3 | * https://github.com/KingSora/OverlayScrollbars 4 | * 5 | * Version: 1.13.0 6 | * 7 | * Copyright KingSora | Rene Haas. 8 | * https://github.com/KingSora 9 | * 10 | * Released under the MIT license. 11 | * Date: 02.08.2020 12 | */ 13 | -------------------------------------------------------------------------------- /docs/8.91de97de.iframe.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * OverlayScrollbars 3 | * https://github.com/KingSora/OverlayScrollbars 4 | * 5 | * Version: 1.13.0 6 | * 7 | * Copyright KingSora | Rene Haas. 8 | * https://github.com/KingSora 9 | * 10 | * Released under the MIT license. 11 | * Date: 02.08.2020 12 | */ 13 | -------------------------------------------------------------------------------- /docs/8.91de97de.iframe.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"8.91de97de.iframe.bundle.js","sources":[],"mappings":";A","sourceRoot":""} -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtindustries/helium/8d96e736d46edda55b4109c8c96ce6a596a56619/docs/favicon.ico -------------------------------------------------------------------------------- /docs/main.2ff507830b5d7d60b7ae.manager.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp = window.webpackJsonp || []).push([ 2 | [2], 3 | { 4 | 547: function (module, exports, __webpack_require__) { 5 | __webpack_require__(548), 6 | __webpack_require__(962), 7 | __webpack_require__(970), 8 | __webpack_require__(971), 9 | __webpack_require__(967), 10 | __webpack_require__(965), 11 | __webpack_require__(964), 12 | __webpack_require__(966), 13 | __webpack_require__(963), 14 | __webpack_require__(968), 15 | __webpack_require__(969), 16 | __webpack_require__(955), 17 | (module.exports = __webpack_require__(958)); 18 | }, 19 | 614: function (module, exports) {} 20 | }, 21 | [[547, 3, 4]] 22 | ]); 23 | -------------------------------------------------------------------------------- /docs/vendors~main.530a53d0.iframe.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendors~main.530a53d0.iframe.bundle.js","sources":[],"mappings":";A","sourceRoot":""} -------------------------------------------------------------------------------- /i18n/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import en from './en.json'; 4 | 5 | export const resources = { 6 | en: { 7 | translation: en 8 | } 9 | } as const; 10 | 11 | i18n.use(initReactI18next).init({ 12 | resources 13 | }); 14 | 15 | export { i18n }; 16 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "npm", 3 | "packages": [ 4 | "packages/*", "tooling/*" 5 | ], 6 | "command": { 7 | "publish": { 8 | "ignoreChanges": ["*.md"] 9 | } 10 | }, 11 | "version": "independent" 12 | } 13 | -------------------------------------------------------------------------------- /packages/cart/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "https://home.ti.test/helium" 3 | # # use the local copy of schema introspected from remote schema 4 | # schema: "graphql.schema.json" 5 | documents: "src/**/*.graphql" 6 | config: 7 | avoidOptionals: false 8 | maybeValue: T 9 | inlineFragmentTypes: 'combine' 10 | scalars: 11 | Slug: 'string' 12 | Date: 'string' 13 | JSON: 'any' 14 | AbsoluteOrRelativeURL: 'string' 15 | HexColor: 'string' 16 | RelativeURL: 'string' 17 | URL: 'string' 18 | UUID: 'string' 19 | strictScalars: true 20 | omitObjectTypes: false 21 | preResolveTypes: true 22 | generates: 23 | # use this to re-generate global types from schema 24 | src/core/graphql/global-types.ts: 25 | plugins: 26 | - "@thoughtindustries/graphql-codegen-plugin" 27 | src/: 28 | preset: near-operation-file 29 | presetConfig: 30 | # use a reduced global types scoped for content 31 | baseTypesPath: core/graphql/global-types.ts 32 | extension: .generated.tsx 33 | plugins: 34 | - "typescript-operations" 35 | - "typescript-react-apollo" 36 | # # use this to re-generate schema from remote schema 37 | # ./graphql.schema.json: 38 | # plugins: 39 | # - "introspection" 40 | -------------------------------------------------------------------------------- /packages/cart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/cart", 3 | "version": "1.2.5", 4 | "description": "A base component for cart", 5 | "author": "Lu Jiang ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": { 20 | "generate": "graphql-codegen --config codegen.yml" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/thoughtindustries/helium/issues" 24 | }, 25 | "dependencies": { 26 | "@headlessui/react": "~1.5.0", 27 | "@thoughtindustries/content": "^1.2.5", 28 | "buffer": "^6.0.3", 29 | "clsx": "^1.1.1", 30 | "couponable": "^7.0.0", 31 | "i18next": "^21.10.0", 32 | "react": "^17 || ^18", 33 | "react-dom": "^17 || ^18", 34 | "react-i18next": "^11.18.6", 35 | "universal-cookie": "^4.0.4" 36 | }, 37 | "peerDependencies": { 38 | "react": "^17 || ^18", 39 | "react-dom": "^17 || ^18" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/cart/src/cart-button.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { useCart, useCartUI } from './core'; 4 | 5 | const CartButton = (): JSX.Element => { 6 | const { items } = useCart(); 7 | const { toggleCart } = useCartUI(); 8 | const { t } = useTranslation(); 9 | const handleClick = useCallback(() => { 10 | toggleCart(); 11 | }, []); 12 | return ( 13 | 21 | ); 22 | }; 23 | 24 | CartButton.displayName = 'CartButton'; 25 | export default CartButton; 26 | -------------------------------------------------------------------------------- /packages/cart/src/cart.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import CartModal from './cart-modal'; 3 | import { CartUIProvider } from './core'; 4 | import { CartProps } from './types'; 5 | 6 | const Cart: FC = ({ 7 | children, 8 | priceFormat, 9 | companyDefaultLocale, 10 | currencyCode, 11 | ...passThroughProps 12 | }) => ( 13 | 14 | {children} 15 | 20 | 21 | ); 22 | 23 | Cart.displayName = 'Cart'; 24 | 25 | export default Cart; 26 | -------------------------------------------------------------------------------- /packages/cart/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_LOCALE = 'en-US'; 2 | export const DEFAULT_CURRENCY_CODE = 'USD'; 3 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/add-to-cart-button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AddToCartButton } from './button'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/add-to-cart-button/types.ts: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes, ReactNode } from 'react'; 2 | import { AddPurchaseableItemPayload } from '../cart-provider'; 3 | 4 | export type AddToCartButtonProps = Omit, 'onClick'> & 5 | AddPurchaseableItemPayload & { 6 | /** any ReactNode children */ 7 | children: ReactNode; 8 | /** option to open cart modal as a follow-up action */ 9 | shouldOpenCart?: boolean; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-checkout-button/button.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | import { useCart } from '../../hooks/use-cart'; 3 | import { useCartCheckout } from '../../hooks/use-cart-checkout'; 4 | import { CartCheckoutButtonProps } from './types'; 5 | 6 | /** 7 | * The `CartCheckoutButton` component renders a button that redirects to the checkout URL for the cart. 8 | * It must be a descendent of a `CartProvider` component. 9 | */ 10 | const CartCheckoutButton = forwardRef( 11 | ({ children, ...passThroughProps }, ref) => { 12 | const { isInitialized } = useCart(); 13 | const { isCheckoutRequested, startCheckout } = useCartCheckout(); 14 | const disabled = !isInitialized || isCheckoutRequested || passThroughProps.disabled; 15 | 16 | return ( 17 | 20 | ); 21 | } 22 | ); 23 | 24 | CartCheckoutButton.displayName = 'CartCheckoutButton'; 25 | export default CartCheckoutButton; 26 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-checkout-button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CartCheckoutButton } from './button'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-checkout-button/types.ts: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes, ReactNode } from 'react'; 2 | 3 | export type CartCheckoutButtonProps = Omit, 'onClick'> & { 4 | /** any ReactNode children */ 5 | children: ReactNode; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-provider/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_CART_ITEM_QUANTITY = 1; 2 | export const CART_COOKIE_NAME = 'cartv2'; 3 | export const CART_ID = 'cart'; 4 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-provider/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { CartContextType } from './types'; 3 | 4 | const CartContext = createContext(undefined); 5 | 6 | export default CartContext; 7 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-provider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CartProvider } from './provider'; 2 | export { default as CartContext } from './context'; 3 | export * from './types'; 4 | export { 5 | getCartItemTotalDueNow, 6 | getCartItemTotalRecurring, 7 | getCartTotalDueNow, 8 | isRecurringCartItem, 9 | isCartItemFree, 10 | isCartFree 11 | } from './utilities'; 12 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-ui-provider/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { CartUIContextType } from './types'; 3 | 4 | const CartUIContext = createContext(undefined); 5 | 6 | export default CartUIContext; 7 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-ui-provider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CartUIProvider } from './provider'; 2 | export { default as CartUIContext } from './context'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/cart-ui-provider/types.ts: -------------------------------------------------------------------------------- 1 | import { CartProviderProps } from '../cart-provider'; 2 | 3 | export type CartUIProviderProps = CartProviderProps; 4 | 5 | export interface CartUIContextType { 6 | /** the open status of the cart */ 7 | isCartOpen: boolean; 8 | /** a callback to open cart */ 9 | openCart: VoidFunction; 10 | /** a callback to close cart */ 11 | closeCart: VoidFunction; 12 | /** a callback to toggle cart open status */ 13 | toggleCart: VoidFunction; 14 | } 15 | -------------------------------------------------------------------------------- /packages/cart/src/core/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-to-cart-button'; 2 | export * from './cart-provider'; 3 | export * from './cart-ui-provider'; 4 | export * from './cart-checkout-button'; 5 | -------------------------------------------------------------------------------- /packages/cart/src/core/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export * as GlobalTypes from './global-types'; 2 | 3 | export * from './queries'; 4 | -------------------------------------------------------------------------------- /packages/cart/src/core/graphql/queries/RelatedProducts.graphql: -------------------------------------------------------------------------------- 1 | query RelatedProducts($productIds: [ID!]!, $courseIds: [ID!]!) { 2 | RelatedProducts(productIds: $productIds, courseIds: $courseIds) { 3 | id 4 | asset 5 | name 6 | suggestedRetailPriceInCents 7 | priceInCents 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cart/src/core/graphql/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RelatedProducts.generated'; 2 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-cart'; 2 | export * from './use-cart-checkout'; 3 | export * from './use-cart-ui'; 4 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart-checkout/hook.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | import { useCart } from '../use-cart'; 3 | import { CartCheckoutBehavior } from './types'; 4 | import { serializeCartItems } from './utilities'; 5 | 6 | /** 7 | * The `useCartCheckout` hook provides access to the cart checkout process. 8 | * It must be a descendent of a `CartProvider` component. 9 | */ 10 | export function useCartCheckout(): CartCheckoutBehavior { 11 | const [requestedCheckout, setRequestedCheckout] = useState(false); 12 | const { isInitialized, checkoutUrl, items } = useCart(); 13 | const startCheckout = useCallback(() => setRequestedCheckout(true), []); 14 | 15 | useEffect(() => { 16 | if (requestedCheckout && checkoutUrl && isInitialized) { 17 | window.location.href = `${checkoutUrl}?cart=${encodeURIComponent(serializeCartItems(items))}`; 18 | } 19 | }, [requestedCheckout, isInitialized, checkoutUrl, items]); 20 | 21 | return { 22 | isCheckoutRequested: requestedCheckout, 23 | startCheckout 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart-checkout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hook'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart-checkout/types.ts: -------------------------------------------------------------------------------- 1 | export interface CartCheckoutBehavior { 2 | isCheckoutRequested: boolean; 3 | startCheckout: () => void; 4 | } 5 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart-checkout/utilities.ts: -------------------------------------------------------------------------------- 1 | import { CartItem } from '../../components/cart-provider'; 2 | 3 | export const serializeCartItems = (items: CartItem[]): string => { 4 | const itemsWithPluckedFields = items.map( 5 | ({ 6 | purchasableId, 7 | purchasableType, 8 | quantity, 9 | isBulkPurchase, 10 | couponCode, 11 | interval, 12 | variationLabel, 13 | courses, 14 | priceInCents 15 | }) => ({ 16 | purchasableId, 17 | purchasableType, 18 | quantity, 19 | isBulkPurchase, 20 | couponCode, 21 | interval, 22 | variationLabel, 23 | courses, 24 | priceInCents: priceInCents || undefined 25 | }) 26 | ); 27 | return JSON.stringify(itemsWithPluckedFields); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart-ui/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CartUIContext } from '../../components/cart-ui-provider'; 3 | 4 | /** 5 | * The `useCartUI` hook provides access to the cart ui context. It must be a descendent of a `CartUIProvider` component. 6 | */ 7 | export function useCartUI() { 8 | const context = React.useContext(CartUIContext); 9 | 10 | if (!context) { 11 | throw new Error('Expected a Cart UI Context, but no Cart UI Context was found'); 12 | } 13 | 14 | return context; 15 | } 16 | -------------------------------------------------------------------------------- /packages/cart/src/core/hooks/use-cart/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CartContext } from '../../components/cart-provider'; 3 | 4 | /** 5 | * The `useCart` hook provides access to the cart context. It must be a descendent of a `CartProvider` component. 6 | */ 7 | export function useCart() { 8 | const context = React.useContext(CartContext); 9 | 10 | if (!context) { 11 | throw new Error('Expected a Cart Context, but no Cart Context was found'); 12 | } 13 | 14 | return context; 15 | } 16 | -------------------------------------------------------------------------------- /packages/cart/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './graphql'; 3 | export * from './hooks'; 4 | -------------------------------------------------------------------------------- /packages/cart/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export { default as CartButton } from './cart-button'; 3 | export { default as Cart } from './cart'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/cart/src/types.ts: -------------------------------------------------------------------------------- 1 | import { CartUIProviderProps } from './core'; 2 | 3 | export type PriceFormatFn = (priceInCents: number) => string; 4 | 5 | export type CartProps = CartUIProviderProps & { 6 | /** optional function for prioritized price formatting */ 7 | priceFormat?: PriceFormatFn; 8 | /** company property to format price */ 9 | companyDefaultLocale?: string; 10 | /** currency code to format price */ 11 | currencyCode?: string; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/catalog/src/catalog-error.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import { useCatalog } from './core'; 3 | 4 | const CatalogError = ({ children }: { children: ReactElement }): JSX.Element => { 5 | const { params } = useCatalog(); 6 | const { error } = params; 7 | 8 | if (error) { 9 | return <>{error}; 10 | } 11 | 12 | return children; 13 | }; 14 | 15 | CatalogError.displayName = 'CatalogError'; 16 | export default CatalogError; 17 | -------------------------------------------------------------------------------- /packages/catalog/src/catalog-loader.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import { LoadingDots } from '@thoughtindustries/content'; 3 | import { useCatalog } from './core'; 4 | 5 | interface CatalogLoaderProps { 6 | children: ReactElement; 7 | } 8 | 9 | const CatalogLoader = ({ children }: CatalogLoaderProps): JSX.Element => { 10 | const { isLoading } = useCatalog(); 11 | 12 | if (isLoading) { 13 | return ; 14 | } 15 | 16 | return children; 17 | }; 18 | 19 | CatalogLoader.displayName = 'CatalogLoader'; 20 | export default CatalogLoader; 21 | -------------------------------------------------------------------------------- /packages/catalog/src/catalog-pagination.tsx: -------------------------------------------------------------------------------- 1 | import React, { cloneElement } from 'react'; 2 | import { CatalogLinkButton, useCatalog } from './core'; 3 | import { CatalogProps } from './types'; 4 | import { Pagination } from '@thoughtindustries/pagination'; 5 | 6 | type CatalogPaginationProps = Pick; 7 | 8 | const CatalogPagination = ({ pagination }: CatalogPaginationProps): JSX.Element | null => { 9 | const { params, urlManager } = useCatalog(); 10 | const { page = 1, pageSize, total } = params; 11 | 12 | if (!total) { 13 | return null; 14 | } 15 | 16 | // derived values 17 | const getPageLink = urlManager.composeURLForSetPage.bind(urlManager); 18 | const props = { 19 | page, 20 | pageSize, 21 | total, 22 | getPageLink, 23 | linkComponent: CatalogLinkButton 24 | }; 25 | 26 | if (pagination) { 27 | return cloneElement(pagination({ ...props })); 28 | } 29 | 30 | return ; 31 | }; 32 | 33 | CatalogPagination.displayName = 'CatalogPagination'; 34 | export default CatalogPagination; 35 | -------------------------------------------------------------------------------- /packages/catalog/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_LOCALE = 'en-US'; 2 | export const DEFAULT_CURRENCY_CODE = 'USD'; 3 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-link-button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CatalogLinkButton } from './link'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-link-button/types.ts: -------------------------------------------------------------------------------- 1 | import { AnchorHTMLAttributes } from 'react'; 2 | 3 | export type CatalogLinkButtonProps = Omit, 'onClick'>; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-provider/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { CatalogContextType } from './types'; 3 | 4 | const CatalogContext = createContext(undefined); 5 | 6 | export default CatalogContext; 7 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-provider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CatalogProvider } from './provider'; 2 | export { default as CatalogContext } from './context'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-provider/provider.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { CatalogProviderProps } from './types'; 3 | import CatalogServerProvider from './server-provider'; 4 | import CatalogClientProvider from './client-provider'; 5 | 6 | /** 7 | * Catalog provider renders nested providers based on prop `ssr`. The nested providers 8 | * differ in where the data-fetching takes place, either in server or client contexts. 9 | * Both nested providers are for internal use only. 10 | */ 11 | const CatalogProvider: FC = ({ children, ...restProps }) => { 12 | const { ssr } = restProps; 13 | 14 | if (ssr) { 15 | return {children}; 16 | } 17 | 18 | return {children}; 19 | }; 20 | 21 | export default CatalogProvider; 22 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/catalog-provider/types.ts: -------------------------------------------------------------------------------- 1 | import { CatalogParams } from '../../utilities/parse-catalog-data'; 2 | import { CatalogParsedURL, CatalogURLManager } from '../../utilities/manage-catalog-url'; 3 | import { ReactNode, RefObject } from 'react'; 4 | 5 | export type navigateClientSideAsyncFnParams = { 6 | url: string; 7 | pushToUrl?: boolean; 8 | }; 9 | 10 | export type CatalogContextType = { 11 | params: CatalogParams; 12 | urlManager: CatalogURLManager; 13 | ssr: boolean; 14 | navigateClientSideAsync?: (params: navigateClientSideAsyncFnParams) => Promise; 15 | isLoading: boolean; 16 | scrollToRef?: RefObject; 17 | contentWrapperRef?: RefObject; 18 | }; 19 | 20 | export type CatalogProviderProps = CatalogParsedURL & { 21 | children: ReactNode; 22 | layoutId?: string; 23 | widgetId?: string; 24 | ssr: boolean; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/height-equalizer/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TIMEOUT = 500; 2 | export const DEFAULT_ANIMATION_SPEED = 0.25; 3 | export const DEFAULT_ELEMENT_TYPE = 'div'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/height-equalizer/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { HeightEqualizerContextType } from './types'; 3 | 4 | const HeightEqualizerContext = createContext(undefined); 5 | 6 | export default HeightEqualizerContext; 7 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/height-equalizer/hook.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeightEqualizerContext from './context'; 3 | 4 | /** 5 | * The `useHeightEqualizer` hook provides access to the height equalizer context. It must be a descendent of a `HeightEqualizer` component. 6 | */ 7 | export default function useHeightEqualizer() { 8 | const context = React.useContext(HeightEqualizerContext); 9 | 10 | if (!context) { 11 | throw new Error('Expected a Height Equalizer Context, but no context was found'); 12 | } 13 | 14 | return context; 15 | } 16 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/height-equalizer/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HeightEqualizer } from './height-equalizer'; 2 | export { default as HeightEqualizerElement } from './height-equalizer-element'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './catalog-link-button'; 2 | export * from './catalog-provider'; 3 | export * from './height-equalizer'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-catalog'; 2 | -------------------------------------------------------------------------------- /packages/catalog/src/core/hooks/use-catalog/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useCatalog } from './use-catalog'; 2 | -------------------------------------------------------------------------------- /packages/catalog/src/core/hooks/use-catalog/use-catalog.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CatalogContext } from '../../components/catalog-provider'; 3 | 4 | /** 5 | * The `useCatalog` hook provides access to the catalog context. It must be a descendent of a `CatalogProvider` component. 6 | */ 7 | export default function useCatalog() { 8 | const context = React.useContext(CatalogContext); 9 | 10 | if (!context) { 11 | throw new Error('Expected a Catalog Context, but no Catalog Context was found'); 12 | } 13 | 14 | return context; 15 | } 16 | -------------------------------------------------------------------------------- /packages/catalog/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './hooks'; 3 | export * from './utilities'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './manage-catalog-url'; 2 | export * from './parse-catalog-data'; 3 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/manage-catalog-url/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CatalogURLManager } from './catalog-url-manager'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/manage-catalog-url/types.ts: -------------------------------------------------------------------------------- 1 | import { AggregationFilter } from '../parse-catalog-data'; 2 | 3 | export type CatalogParsedURL = { 4 | pathName: string; 5 | searchString?: string; 6 | }; 7 | 8 | export type AggregationFilterWithComposedURL = { 9 | filter: AggregationFilter; 10 | url: string; 11 | }; 12 | 13 | export type ContentTypeWithComposedURL = { 14 | contentType: string; 15 | url: string; 16 | }; 17 | 18 | export enum CatalogURLSearchParams { 19 | Token = 'token', 20 | SearchTerm = 'query', 21 | AggregationLabels = 'labels', 22 | AggregationValues = 'values', 23 | ContentTypes = 'content-types', 24 | DisplayType = 'display-type', 25 | Page = 'page', 26 | Sort = 'sort' 27 | } 28 | 29 | export type SearchTermFormHiddenField = { 30 | name: string; 31 | value: string; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/constants.ts: -------------------------------------------------------------------------------- 1 | import { CatalogParams } from './types'; 2 | 3 | export const DEFAULT_PAGE = 1; 4 | export const DEFAULT_PAGE_SIZE = 48; 5 | export const SORT_DELIMITER = ':'; 6 | 7 | export const DEFAULT_PARAMS: CatalogParams = { 8 | // default request params 9 | page: DEFAULT_PAGE, 10 | aggregationFilters: [], 11 | contentTypes: [], 12 | // default response data 13 | results: [], 14 | aggregations: [], 15 | hasMore: false, 16 | isCurated: false, 17 | enabledSorts: [], 18 | enabledDisplayTypes: [], 19 | resultContentTypes: [], 20 | contentTypeFilterEnabled: false, 21 | displayStartDateEnabled: false, 22 | displayAuthorsEnabled: false, 23 | displayDescriptionOnCalendar: false, 24 | // default general params 25 | pageSize: DEFAULT_PAGE_SIZE 26 | }; 27 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | 3 | export { default as parseAggregationFilters } from './parse-aggregation-filters'; 4 | export { default as parseQueryVariables } from './parse-query-variables'; 5 | export { default as parseSort } from './parse-sort'; 6 | export { default as parseResponseData } from './parse-response-data'; 7 | export { default as serializeSort } from './serialize-sort'; 8 | 9 | export * from './constants'; 10 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/parse-aggregation-filters.ts: -------------------------------------------------------------------------------- 1 | import { AggregationFilter } from './types'; 2 | 3 | type LabelValueParam = { 4 | labels: string[]; 5 | values: string[]; 6 | }; 7 | 8 | const parseAggregationFilters = (filters: AggregationFilter[]): LabelValueParam => { 9 | return filters.reduce( 10 | (acc, { label, value }) => { 11 | acc.labels.push(label); 12 | acc.values.push(value); 13 | return acc; 14 | }, 15 | { labels: [], values: [] } as LabelValueParam 16 | ); 17 | }; 18 | 19 | export default parseAggregationFilters; 20 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/parse-query-variables.ts: -------------------------------------------------------------------------------- 1 | import { CatalogContentQueryVariables } from '@thoughtindustries/content'; 2 | import { DEFAULT_PAGE } from './constants'; 3 | import parseAggregationFilters from './parse-aggregation-filters'; 4 | import { CatalogParams } from './types'; 5 | 6 | const parseQueryVariables = (params: CatalogParams): CatalogContentQueryVariables => { 7 | const { 8 | page = DEFAULT_PAGE, 9 | token, 10 | sort, 11 | displayType, 12 | resultsDisplayType, 13 | aggregationFilters, 14 | searchTerm, 15 | contentTypes 16 | } = params; 17 | const sortColumn = sort?.field; 18 | const sortDirection = sort?.direction; 19 | const displayTypeParam = displayType || resultsDisplayType; 20 | const transformedFilters = parseAggregationFilters(aggregationFilters); 21 | return { 22 | page, 23 | sortColumn, 24 | sortDirection, 25 | resultsDisplayType: displayTypeParam, 26 | token, 27 | contentTypes, 28 | query: searchTerm, 29 | labels: transformedFilters.labels, 30 | values: transformedFilters.values 31 | }; 32 | }; 33 | 34 | export default parseQueryVariables; 35 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/parse-sort.ts: -------------------------------------------------------------------------------- 1 | import { SortDirection, SortField } from '../..'; 2 | import { SORT_DELIMITER } from './constants'; 3 | import { Sort } from './types'; 4 | 5 | const parseSort = (sort: string): Sort | undefined => { 6 | if (!sort) { 7 | return; 8 | } 9 | 10 | const splitSort = sort.split(SORT_DELIMITER); 11 | 12 | if (!splitSort.length) { 13 | return; 14 | } 15 | 16 | const field = splitSort[0] as SortField; 17 | 18 | if (!field) { 19 | return; 20 | } 21 | 22 | const direction = splitSort.length > 1 ? (splitSort[1] as SortDirection) : undefined; 23 | 24 | return { 25 | field, 26 | direction 27 | }; 28 | }; 29 | 30 | export default parseSort; 31 | -------------------------------------------------------------------------------- /packages/catalog/src/core/utilities/parse-catalog-data/serialize-sort.ts: -------------------------------------------------------------------------------- 1 | import { SORT_DELIMITER } from './constants'; 2 | import { Sort } from './types'; 3 | 4 | const truthyFilter = (x: T | false | undefined | '' | 0): x is T => !!x; 5 | 6 | const serializeSort = (sort: Sort | string): string => { 7 | if (typeof sort === 'string') { 8 | return sort; 9 | } 10 | const { field, direction } = sort; 11 | return [field, direction].filter(truthyFilter).join(SORT_DELIMITER); 12 | }; 13 | 14 | export default serializeSort; 15 | -------------------------------------------------------------------------------- /packages/catalog/src/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const ArrowDownIcon = (): JSX.Element => ( 4 | 11 | 12 | 13 | ); 14 | 15 | export const ArrowRightIcon = (): JSX.Element => ( 16 | 23 | 24 | 25 | ); 26 | 27 | export const CheckIcon = (): JSX.Element => ( 28 | 35 | 36 | 37 | ); 38 | -------------------------------------------------------------------------------- /packages/catalog/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export { default as Catalog } from './catalog'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-icon/calendar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | const DisplayTypeIconCalendar = ({ 5 | isActive, 6 | link 7 | }: { 8 | isActive: boolean; 9 | link: string; 10 | }): JSX.Element => ( 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | 29 | DisplayTypeIconCalendar.displayName = 'DisplayTypeIconCalendar'; 30 | 31 | export default DisplayTypeIconCalendar; 32 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-icon/grid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | const DisplayTypeIconGrid = ({ 5 | isActive, 6 | link 7 | }: { 8 | isActive: boolean; 9 | link: string; 10 | }): JSX.Element => ( 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | 28 | DisplayTypeIconGrid.displayName = 'DisplayTypeIconGrid'; 29 | 30 | export default DisplayTypeIconGrid; 31 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-icon/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DisplayTypeIconCalendar } from './calendar'; 2 | export { default as DisplayTypeIconGrid } from './grid'; 3 | export { default as DisplayTypeIconList } from './list'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-icon/list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | const DisplayTypeIconList = ({ 5 | isActive, 6 | link 7 | }: { 8 | isActive: boolean; 9 | link: string; 10 | }): JSX.Element => ( 11 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | DisplayTypeIconList.displayName = 'DisplayTypeIconList'; 31 | 32 | export default DisplayTypeIconList; 33 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-results/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DisplayTypeResultsCalendar } from './calendar'; 2 | export { default as DisplayTypeResultsGrid } from './grid'; 3 | export { default as DisplayTypeResultsList } from './list'; 4 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-results/item-asset-block.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | interface ItemAssetBlockProps { 5 | asset?: string; 6 | classNames?: string; 7 | } 8 | 9 | const ItemAssetBlock = ({ asset, classNames = '' }: ItemAssetBlockProps): JSX.Element => ( 10 | 17 | ); 18 | 19 | ItemAssetBlock.displayName = 'ItemAssetBlock'; 20 | 21 | export default ItemAssetBlock; 22 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-results/item-link-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, SyntheticEvent, useCallback } from 'react'; 2 | import { CatalogResultItem, CatalogResultsProps } from '../../types'; 3 | 4 | type ItemLinkWrapperProps = Pick & { 5 | children: ReactNode; 6 | item: CatalogResultItem; 7 | }; 8 | 9 | const ItemLinkWrapper = ({ children, onClick, item }: ItemLinkWrapperProps): JSX.Element => { 10 | const { isActive, href } = item; 11 | 12 | const itemIsActiveOrWebinarOrEvent = !!isActive; 13 | 14 | const handleClick = useCallback( 15 | (evt: SyntheticEvent) => { 16 | onClick && onClick(evt, item); 17 | }, 18 | [item, onClick] 19 | ); 20 | const linkProps: { 21 | className: string; 22 | href?: string; 23 | onClick: (evt: SyntheticEvent) => void; 24 | target?: string; 25 | } = { 26 | href, 27 | onClick: handleClick, 28 | className: `block text-gray-800 ${!itemIsActiveOrWebinarOrEvent ? 'cursor-default' : ''}` 29 | }; 30 | return {children}; 31 | }; 32 | 33 | ItemLinkWrapper.displayName = 'ItemLinkWrapper'; 34 | 35 | export default ItemLinkWrapper; 36 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/display-type-results/utilities.ts: -------------------------------------------------------------------------------- 1 | export const limitText = (text: string, maxLength: number): string => { 2 | if (text.length > maxLength) { 3 | return `${text.substring(0, maxLength)}...`; 4 | } 5 | return text; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter-selector/constants.ts: -------------------------------------------------------------------------------- 1 | import { SortField } from '../../core'; 2 | import { GlobalTypes } from '@thoughtindustries/content'; 3 | 4 | export const localizedSortMapping: { [key in SortField]: string } = { 5 | [GlobalTypes.SortColumn.UpdatedAt]: 'catalog.sort-updated', 6 | [GlobalTypes.SortColumn.CreatedAt]: 'catalog.sort-created', 7 | [GlobalTypes.SortColumn.Title]: 'catalog.sort-title', 8 | [GlobalTypes.SortColumn.PublishDate]: 'catalog.sort-publish-date', 9 | [GlobalTypes.SortColumn.CourseStartDate]: 'catalog.sort-course-start-date', 10 | [GlobalTypes.SortColumn.Relevance]: 'catalog.sort-relevance' 11 | }; 12 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter-selector/content-type-selector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { CatalogParams, useCatalog } from '../../core'; 4 | import DropdownMenu from './dropdown-menu'; 5 | 6 | const ContentTypeSelector = ({ 7 | contentTypes, 8 | resultContentTypes 9 | }: Pick): JSX.Element => { 10 | const { t } = useTranslation(); 11 | const { urlManager } = useCatalog(); 12 | const contentTypeFieldLabel = t('filter-by'); 13 | const filters = resultContentTypes 14 | .filter(item => !contentTypes.includes(item)) 15 | .map(item => ({ 16 | name: item, 17 | link: urlManager.composeURLForAddContentType(item) 18 | })); 19 | 20 | return ; 21 | }; 22 | 23 | ContentTypeSelector.displayName = 'ContentTypeSelector'; 24 | export default ContentTypeSelector; 25 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter-selector/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ContentTypeSelector } from './content-type-selector'; 2 | export { default as DisplayTypeSelector } from './display-type-selector'; 3 | export { default as SearchInput } from './search-input'; 4 | export { default as SortSelector } from './sort-selector'; 5 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter-selector/sort-selector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { CatalogParams, useCatalog } from '../../core'; 4 | import { localizedSortMapping } from './constants'; 5 | import DropdownMenu from './dropdown-menu'; 6 | 7 | const SortSelector = ({ 8 | enabledSorts, 9 | sort 10 | }: Pick): JSX.Element => { 11 | const { t } = useTranslation(); 12 | const { urlManager } = useCatalog(); 13 | const { field: selectedField, direction: selectedDirection } = sort || {}; 14 | const label = t('catalog.sort-by'); 15 | const filters = enabledSorts.map(item => ({ 16 | isSelected: selectedField === item.field && selectedDirection === item.direction, 17 | name: t(localizedSortMapping[item.field]), 18 | link: urlManager.composeURLForSetSort(item) 19 | })); 20 | 21 | return ; 22 | }; 23 | 24 | SortSelector.displayName = 'SortSelector'; 25 | export default SortSelector; 26 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/aggregation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from './link'; 3 | 4 | const FilterAggregation = ({ 5 | label, 6 | removeFilterHref 7 | }: { 8 | label: string; 9 | removeFilterHref: string; 10 | }): JSX.Element => ; 11 | 12 | FilterAggregation.displayName = 'FilterAggregation'; 13 | 14 | export default FilterAggregation; 15 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/content-type.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CatalogParams } from '../../core'; 3 | import Link from './link'; 4 | 5 | const FilterContentType = ({ 6 | contentType, 7 | removeFilterHref 8 | }: { 9 | contentType: CatalogParams['contentTypes'][0]; 10 | removeFilterHref: string; 11 | }): JSX.Element => ; 12 | 13 | FilterContentType.displayName = 'FilterContentType'; 14 | 15 | export default FilterContentType; 16 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FilterAggregation } from './aggregation'; 2 | export { default as FilterContentType } from './content-type'; 3 | export { default as FilterSearchTerm } from './search-term'; 4 | export { default as FilterTokenLabel } from './token-label'; 5 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/link.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Wrapper from './wrapper'; 3 | import RemoveIcon from './remove-icon'; 4 | import { CatalogLinkButton } from '../../core'; 5 | 6 | const FilterLink = ({ label, href }: { label: string; href: string }): JSX.Element => ( 7 | 8 | 12 | 13 | 14 | 15 | {label} 16 | 17 | 18 | ); 19 | 20 | FilterLink.displayName = 'FilterLink'; 21 | 22 | export default FilterLink; 23 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/remove-icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const RemoveIcon = (): JSX.Element => ( 4 | 11 | 12 | 13 | ); 14 | 15 | export default RemoveIcon; 16 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/search-term.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CatalogParams } from '../../core'; 3 | import Link from './link'; 4 | 5 | const FilterSearchTerm = ({ 6 | searchTerm, 7 | removeFilterHref 8 | }: { 9 | searchTerm: CatalogParams['searchTerm']; 10 | removeFilterHref: string; 11 | }): JSX.Element => ; 12 | 13 | FilterSearchTerm.displayName = 'FilterSearchTerm'; 14 | 15 | export default FilterSearchTerm; 16 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/token-label.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CatalogParams } from '../../core'; 3 | import Link from './link'; 4 | 5 | const FilterTokenLabel = ({ 6 | tokenLabel, 7 | removeFilterHref 8 | }: { 9 | tokenLabel: CatalogParams['tokenLabel']; 10 | removeFilterHref: string; 11 | }): JSX.Element => ; 12 | 13 | FilterTokenLabel.displayName = 'FilterTokenLabel'; 14 | 15 | export default FilterTokenLabel; 16 | -------------------------------------------------------------------------------- /packages/catalog/src/variants/filter/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react'; 2 | 3 | interface FilterWrapperProps { 4 | children: ReactNode; 5 | } 6 | const FilterWrapper: FC = ({ children }) => ( 7 | {children} 8 | ); 9 | 10 | FilterWrapper.displayName = 'FilterWrapper'; 11 | 12 | export default FilterWrapper; 13 | -------------------------------------------------------------------------------- /packages/contact-block/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/contact-block` 2 | 3 | > A Contact Block is a block of information used to display contact information. Normally placed towards the bottom of a web page, above on in the footer. It often consists of information that can be used to contact the sites owners or admins.. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { ContactBlock } from '@thoughtindustries/contact-block'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/contact-block/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/contact-block", 3 | "version": "1.1.2", 4 | "description": "A base component for contact-block", 5 | "author": "Jamal-majid-ti ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": { 20 | "test": "echo \"Error: run tests from root\" && exit 1" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/thoughtindustries/helium/issues" 24 | }, 25 | "dependencies": { 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0" 28 | }, 29 | "peerDependencies": { 30 | "react": "^17 || ^18", 31 | "react-dom": "^17 || ^18" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/contact-block/src/index.ts: -------------------------------------------------------------------------------- 1 | import ContactBlock from './contact-block'; 2 | 3 | export * from './types'; 4 | export { ContactBlock }; 5 | -------------------------------------------------------------------------------- /packages/contact-block/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface ContactBlockProps { 2 | /** Asset image for contact block */ 3 | asset?: string; 4 | /** Contact name displayed in contact block */ 5 | contactName?: string; 6 | /** Contact subtitle displayed in contact block */ 7 | contactSubtitle?: string; 8 | /** Contact discription displayed contact block */ 9 | contactDescription?: string; 10 | /** Contact email selection in CTA dropdown */ 11 | contactEmail?: string; 12 | /** CTA button text for contact block */ 13 | actionText?: string; 14 | /** CTA action dropdown for contact block */ 15 | actionType?: string; 16 | /** Url selection string in the CTA dropdown */ 17 | url?: string; 18 | /** Background color displayed in Contact block */ 19 | backgroundColor?: string; 20 | /** Color of text for contact block */ 21 | textColor?: string; 22 | /** Checkbox in contact block for url */ 23 | linkOpenInNewTab?: boolean; 24 | } 25 | -------------------------------------------------------------------------------- /packages/contact-block/stories/contact-block.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | import React from 'react'; 3 | import { ContactBlockProps, ContactBlock } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: ContactBlock, 7 | title: 'Packages/Contact Block' 8 | }; 9 | 10 | export default meta; 11 | type ContactBlock = StoryObj; 12 | 13 | export const Base: ContactBlock = { 14 | render: args => , 15 | args: { 16 | contactSubtitle: 'Subtitle', 17 | contactName: 'Jane Jacobs', 18 | actionText: 'Contact', 19 | contactDescription: 20 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus tristique metus nec sagittis euismod lorem ipsum forte contiuum.' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/content/__tests__/components/loading-dots/loading-dots.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { LoadingDots } from '../../../src/components/loading-dots'; 4 | 5 | describe('@thoughtindustries/content/LoadingDots', () => { 6 | it('should render', () => { 7 | const { container } = render(); 8 | expect(container).toMatchInlineSnapshot(` 9 |
10 |
13 |
17 |
21 |
24 |
25 |
26 | `); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/content/__tests__/utilities/format-time/format-time.test.ts: -------------------------------------------------------------------------------- 1 | import { formatTime } from '../../../src/utilities/format-time'; 2 | 3 | describe('@thoughtindustries/content/formatTime', () => { 4 | const dateISO = new Date(2020, 0, 1, 5).toISOString(); 5 | const format = 'ddd, MMM Do YYYY hh:mm a'; 6 | 7 | const testData = [ 8 | { 9 | expected: 'Tue, Dec 31st 2019 11:00 pm', 10 | date: dateISO, 11 | timeZone: 'America/Chicago', 12 | format 13 | }, 14 | { 15 | expected: 'Wed, Jan 1st 2020 12:00 am', 16 | date: dateISO, 17 | timeZone: undefined, 18 | format 19 | } 20 | ]; 21 | it.each(testData)( 22 | 'should return $expected with date $date, time zone $timeZone and format $format', 23 | ({ expected, date, timeZone, format }) => { 24 | expect(formatTime(date, timeZone, format)).toEqual(expected); 25 | } 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/content/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'https://home.ti.test/helium' 3 | # # use the local copy of schema introspected from remote schema 4 | # schema: "graphql.schema.json" 5 | documents: 'src/**/*.graphql' 6 | config: 7 | avoidOptionals: false 8 | maybeValue: T 9 | inlineFragmentTypes: 'combine' 10 | scalars: 11 | Slug: 'string' 12 | Date: 'string' 13 | JSON: 'any' 14 | AbsoluteOrRelativeURL: 'string' 15 | HexColor: 'string' 16 | RelativeURL: 'string' 17 | URL: 'string' 18 | UUID: 'string' 19 | strictScalars: true 20 | omitObjectTypes: false 21 | preResolveTypes: true 22 | generates: 23 | # use this to re-generate global types from schema 24 | src/graphql/global-types.ts: 25 | plugins: 26 | - '@thoughtindustries/graphql-codegen-plugin' 27 | src/: 28 | preset: near-operation-file 29 | presetConfig: 30 | # use a reduced global types scoped for content 31 | baseTypesPath: graphql/global-types.ts 32 | extension: .generated.tsx 33 | plugins: 34 | - 'typescript-operations' 35 | - 'typescript-react-apollo' 36 | # # use this to re-generate schema from remote schema 37 | # ./graphql.schema.json: 38 | # plugins: 39 | # - "introspection" 40 | -------------------------------------------------------------------------------- /packages/content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/content", 3 | "version": "1.2.5", 4 | "description": "> TODO: description", 5 | "author": "Lu Jiang ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": { 20 | "generate": "graphql-codegen --config codegen.yml" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/thoughtindustries/helium/issues" 24 | }, 25 | "dependencies": { 26 | "@apollo/client": "^3.7.0", 27 | "dayjs": "^1.10.8", 28 | "graphql": "^16.6.0", 29 | "i18next": "^21.10.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/content/src/components/content-header/index.ts: -------------------------------------------------------------------------------- 1 | import ContentHeader from './content-header'; 2 | 3 | export { ContentHeader }; 4 | -------------------------------------------------------------------------------- /packages/content/src/components/content-header/types.ts: -------------------------------------------------------------------------------- 1 | import { ContentKind } from '../../graphql/global-types'; 2 | 3 | export type ContentHeaderProps = { 4 | /** Content Kind for content header */ 5 | contentKind: ContentKind; 6 | /** Content Slug */ 7 | slug: string; 8 | /** Boolean to show stars and rating information */ 9 | showStars?: boolean; 10 | /** Boolean to show content image */ 11 | showImage?: boolean; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/content/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './loading-dots'; 2 | export { ContentHeader } from './content-header'; 3 | -------------------------------------------------------------------------------- /packages/content/src/components/loading-dots/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LoadingDots } from './loading-dots'; 2 | -------------------------------------------------------------------------------- /packages/content/src/components/loading-dots/loading-dots.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LoadingDots = (): JSX.Element => { 4 | const firstDotStyles = { animationDelay: '-0.32s' }; 5 | const secondDotStyles = { animationDelay: '-0.16s' }; 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | LoadingDots.displayName = 'LoadingDots'; 16 | export default LoadingDots; 17 | -------------------------------------------------------------------------------- /packages/content/src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export * as GlobalTypes from './global-types'; 2 | 3 | export * from './queries'; 4 | export * from './mutations'; 5 | -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/AddResourceToQueue.graphql: -------------------------------------------------------------------------------- 1 | mutation AddResourceToQueue($resourceType: ContentKind, $resourceId: ID!) { 2 | AddResourceToQueue(resourceType: $resourceType, resourceId: $resourceId) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/ArchiveUserCourse.graphql: -------------------------------------------------------------------------------- 1 | mutation ArchiveUserCourse($id: ID!) { 2 | ArchiveUserCourse(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/ArchiveUserLearningPath.graphql: -------------------------------------------------------------------------------- 1 | mutation ArchiveUserLearningPath($id: ID!) { 2 | ArchiveUserLearningPath(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/CreateCertificateFromUpload.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateCertificateFromUpload( 2 | $asset: URL! 3 | $certificateUploadFields: [CertificateUploadField!] 4 | ) { 5 | CreateCertificateFromUpload(asset: $asset, certificateUploadFields: $certificateUploadFields) { 6 | __typename 7 | id 8 | externalResourceTitle 9 | } 10 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/DestroyBookmark.graphql: -------------------------------------------------------------------------------- 1 | mutation DestroyBookmark($id: ID!) { 2 | DestroyBookmark(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/DestroyBookmarkFolder.graphql: -------------------------------------------------------------------------------- 1 | mutation DestroyBookmarkFolder($id: ID!) { 2 | DestroyBookmarkFolder(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/ReinstateUserCourse.graphql: -------------------------------------------------------------------------------- 1 | mutation ReinstateUserCourse($id: ID!) { 2 | ReinstateUserCourse(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/ReinstateUserLearningPath.graphql: -------------------------------------------------------------------------------- 1 | mutation ReinstateUserLearningPath($id: ID!) { 2 | ReinstateUserLearningPath(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/UnenrollFromWaitlist.graphql: -------------------------------------------------------------------------------- 1 | mutation UnenrollFromWaitlist($id: ID!) { 2 | UnenrollFromWaitlist(id: $id) 3 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/UpdateBookmark.graphql: -------------------------------------------------------------------------------- 1 | mutation UpdateBookmark($id: ID!, $note: String, $bookmarkFolder: ID!) { 2 | UpdateBookmark(id: $id, note: $note, bookmarkFolder: $bookmarkFolder) { 3 | id 4 | } 5 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/UpdateBookmarkFolder.graphql: -------------------------------------------------------------------------------- 1 | mutation UpdateBookmarkFolder($id: ID!, $name: String!) { 2 | UpdateBookmarkFolder(id: $id, name: $name) { 3 | id 4 | name 5 | } 6 | } -------------------------------------------------------------------------------- /packages/content/src/graphql/mutations/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddResourceToQueue.generated'; 2 | export * from './ArchiveUserCourse.generated'; 3 | export * from './ArchiveUserLearningPath.generated'; 4 | export * from './ReinstateUserLearningPath.generated'; 5 | export * from './ReinstateUserCourse.generated'; 6 | export * from './UnenrollFromWaitlist.generated'; 7 | export * from './UpdateBookmarkFolder.generated'; 8 | export * from './DestroyBookmarkFolder.generated'; 9 | export * from './CreateCertificateFromUpload.generated'; 10 | export * from './UpdateBookmark.generated'; 11 | export * from './DestroyBookmark.generated'; 12 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/CatalogContent.graphql: -------------------------------------------------------------------------------- 1 | query CatalogContent( 2 | $layoutId: ID 3 | $widgetId: ID 4 | $sortColumn: SortColumn 5 | $sortDirection: SortDirection 6 | $resultsDisplayType: ContentItemDisplayType 7 | $page: Int! 8 | $token: String 9 | $labels: [String!] 10 | $values: [String!] 11 | $contentTypes: [String!] 12 | $query: String 13 | ) { 14 | CatalogContent( 15 | layoutId: $layoutId 16 | widgetId: $widgetId 17 | sortColumn: $sortColumn 18 | sortDirection: $sortDirection 19 | resultsDisplayType: $resultsDisplayType 20 | page: $page 21 | token: $token 22 | labels: $labels 23 | values: $values 24 | contentTypes: $contentTypes 25 | query: $query 26 | ) { 27 | contentItems { 28 | ...ContentFragment 29 | location { 30 | ...LocationFragment 31 | } 32 | } 33 | meta { 34 | ...CatalogMetaFragment 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/CatalogMetaFragment.graphql: -------------------------------------------------------------------------------- 1 | fragment CatalogMetaFragment on CatalogMeta { 2 | aggregations { 3 | key 4 | label 5 | buckets { 6 | query 7 | value 8 | label 9 | description 10 | count 11 | } 12 | } 13 | contentTypeFilterEnabled 14 | contentTypes 15 | displayAuthorsEnabled 16 | displayBundle { 17 | id 18 | name 19 | slug 20 | priceInCents 21 | annualPriceInCents 22 | } 23 | displayStartDateEnabled 24 | displayDescriptionOnCalendar 25 | displayTypeCalendarEnabled 26 | displayTypeGridEnabled 27 | displayTypeListEnabled 28 | hasMore 29 | isCurated 30 | queryCustomFields 31 | resultsDisplayType 32 | selectedSortColumn 33 | selectedSortDirection 34 | sortCourseStartDateEnabled 35 | sortCreatedAtEnabled 36 | sortPublishDateEnabled 37 | sortRelevanceEnabled 38 | sortTitleEnabled 39 | sortUpdatedAtEnabled 40 | tokenLabel 41 | total 42 | } 43 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/CatalogQuery.graphql: -------------------------------------------------------------------------------- 1 | query Catalog($query: String, $querySignature: String, $querySort: String) { 2 | CatalogQuery(query: $query, querySignature: $querySignature, querySort: $querySort) { 3 | contentItems { 4 | ...ContentFragment 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/CourseGroupBySlug.graphql: -------------------------------------------------------------------------------- 1 | query CourseGroupBySlug($slug: Slug!) { 2 | CourseGroupBySlug(slug: $slug) { 3 | asset 4 | description 5 | title 6 | rating 7 | ratingsCount 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/Languages.graphql: -------------------------------------------------------------------------------- 1 | query LanguagesQuery { 2 | Languages { 3 | id 4 | label 5 | code 6 | isCustom 7 | selectorLabel 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/LearningPathBySlug.graphql: -------------------------------------------------------------------------------- 1 | query LearningPathBySlug($slug: Slug!) { 2 | LearningPathBySlug(slug: $slug) { 3 | name 4 | shortDescription 5 | asset 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/LocationFragment.generated.tsx: -------------------------------------------------------------------------------- 1 | import * as Types from '../global-types'; 2 | 3 | import { gql } from '@apollo/client'; 4 | export type LocationFragmentFragment = { 5 | __typename?: 'Location'; 6 | id: string; 7 | name: string; 8 | room?: string; 9 | address1: string; 10 | address2?: string; 11 | city: string; 12 | state?: string; 13 | zipCode?: string; 14 | country?: string; 15 | timeZone?: string; 16 | }; 17 | 18 | export const LocationFragmentFragmentDoc = gql` 19 | fragment LocationFragment on Location { 20 | id 21 | name 22 | room 23 | address1 24 | address2 25 | city 26 | state 27 | zipCode 28 | country 29 | timeZone 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/LocationFragment.graphql: -------------------------------------------------------------------------------- 1 | fragment LocationFragment on Location { 2 | id 3 | name 4 | room 5 | address1 6 | address2 7 | city 8 | state 9 | zipCode 10 | country 11 | timeZone 12 | } 13 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/QueryContents.graphql: -------------------------------------------------------------------------------- 1 | query Contents($ids: [ID!]!) { 2 | QueryContents(ids: $ids) { 3 | ...ContentFragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/RssItems.graphql: -------------------------------------------------------------------------------- 1 | query RssItems($feedUrl: String!) { 2 | RssItems(feedUrl: $feedUrl) { 3 | title 4 | link 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserArchives.graphql: -------------------------------------------------------------------------------- 1 | query UserArchives { 2 | UserArchives { 3 | id 4 | user 5 | resource 6 | resourceType 7 | status 8 | archivedAt 9 | name 10 | reinstatable 11 | waitlistActive 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserBookmarks.graphql: -------------------------------------------------------------------------------- 1 | query UserBookmarks { 2 | UserBookmarks { 3 | id 4 | name 5 | defaultFolder 6 | bookmarkCount 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserBookmarksByFolder.graphql: -------------------------------------------------------------------------------- 1 | query UserBookmarksByFolder($id: ID!) { 2 | UserBookmarksByFolder(id: $id) { 3 | id 4 | course { 5 | id 6 | title 7 | slug 8 | status 9 | courseGroup { 10 | id 11 | authors 12 | source 13 | asset 14 | kind 15 | contentType { 16 | label 17 | } 18 | } 19 | } 20 | topicId 21 | note 22 | createdAt 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCertificateFields.graphql: -------------------------------------------------------------------------------- 1 | query UserCertificateFields { 2 | UserCertificateFields { 3 | id 4 | type 5 | label 6 | awardTypeId 7 | awardType { 8 | id 9 | pluralLabel 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCertificates.graphql: -------------------------------------------------------------------------------- 1 | query UserCertificates($query: String, $includeExpiredCertificates: Boolean) { 2 | UserCertificates(query: $query, includeExpiredCertificates: $includeExpiredCertificates) { 3 | id 4 | resourceId 5 | expirationDate 6 | isExpired 7 | externalResourceTitle 8 | url 9 | source 10 | contentItem { 11 | id 12 | asset 13 | courseEndDate 14 | courseStartDate 15 | coursePresold 16 | description 17 | kind 18 | slug 19 | availabilityStatus 20 | contentTypeLabel 21 | title 22 | timeZone 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserContentGroups.graphql: -------------------------------------------------------------------------------- 1 | query ContentGroups($query: String, $includeExpiredCertificates: Boolean) { 2 | UserContentGroups(query: $query, includeExpiredCertificates: $includeExpiredCertificates) { 3 | kind 4 | count 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserContentItems.graphql: -------------------------------------------------------------------------------- 1 | query UserContentItems( 2 | $query: String 3 | $kind: [ContentKind!] 4 | $sortColumn: SortColumn 5 | $sortDirection: SortDirection 6 | ) { 7 | UserContentItems( 8 | query: $query 9 | kind: $kind 10 | sortColumn: $sortColumn 11 | sortDirection: $sortDirection 12 | ) { 13 | asset 14 | title 15 | sessionTitle 16 | kind 17 | id 18 | slug 19 | meetingStartDate 20 | contentTypeLabel 21 | availabilityStatus 22 | courseStartDate 23 | courseEndDate 24 | coursePresold 25 | description 26 | displayCourse 27 | displayCourseSlug 28 | displayDate 29 | courseGracePeriodEnded 30 | authors 31 | publishDate 32 | source 33 | expiresAt 34 | currentUserMayReschedule 35 | timeZone 36 | embeddedEnabled 37 | currentUserUnmetCoursePrerequisites 38 | currentUserUnmetLearningPathPrerequisites 39 | hasChildren 40 | hideCourseDescription 41 | isActive 42 | waitlistingEnabled 43 | waitlistingTriggered 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCourseAwardCounts.graphql: -------------------------------------------------------------------------------- 1 | query UserCourseAwardCounts($courseId: ID!) { 2 | UserCourseAwardCounts(courseId: $courseId) { 3 | id 4 | label 5 | icon 6 | count 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCourseCollaborations.graphql: -------------------------------------------------------------------------------- 1 | query UserCourseCollaborations($courseId: ID!) { 2 | UserCourseCollaborations(courseId: $courseId) 3 | } 4 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCourseCompletionProgress.graphql: -------------------------------------------------------------------------------- 1 | query UserCourseCompletionProgress($id: ID!) { 2 | UserCourseCompletionProgress(id: $id) { 3 | type 4 | required 5 | completed 6 | percent 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserCourseProgress.graphql: -------------------------------------------------------------------------------- 1 | query UserCourseProgress($id: ID!) { 2 | UserCourseProgress(id: $id) { 3 | totalViews 4 | totalTime 5 | percentComplete 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserRecentContent.graphql: -------------------------------------------------------------------------------- 1 | query UserRecentContent($limit: Int) { 2 | UserRecentContent(limit: $limit) { 3 | ...ContentFragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/UserWaitlist.graphql: -------------------------------------------------------------------------------- 1 | query UserWaitlist { 2 | UserWaitlist { 3 | id 4 | contentTypeLabel 5 | title 6 | kind 7 | slug 8 | displayCourse 9 | displayCourseSlug 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/content/src/graphql/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CatalogContent.generated'; 2 | export * from './CatalogMetaFragment.generated'; 3 | export * from './CatalogQuery.generated'; 4 | export * from './ContentFragment.generated'; 5 | export * from './CourseGroupBySlug.generated'; 6 | export * from './LearningPathBySlug.generated'; 7 | export * from './Languages.generated'; 8 | export * from './LocationFragment.generated'; 9 | export * from './QueryContents.generated'; 10 | export * from './RssItems.generated'; 11 | export * from './UserRecentContent.generated'; 12 | export * from './UserContentItems.generated'; 13 | export * from './UserArchives.generated'; 14 | export * from './UserWaitlist.generated'; 15 | export * from './UserBookmarks.generated'; 16 | export * from './UserCertificates.generated'; 17 | export * from './UserContentGroups.generated'; 18 | export * from './UserBookmarksByFolder.generated'; 19 | export * from './UserCourseCompletionProgress.generated'; 20 | export * from './UserCourseProgress.generated'; 21 | export * from './UserCourseCompletionProgress.generated'; 22 | export * from './UserCourseAwardCounts.generated'; 23 | export * from './UserCourseCollaborations.generated'; 24 | export * from './UserCertificateFields.generated'; 25 | -------------------------------------------------------------------------------- /packages/content/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './graphql'; 3 | export * from './utilities'; 4 | -------------------------------------------------------------------------------- /packages/content/src/utilities/format-time/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TIMEZONE = 'America/New_York'; 2 | -------------------------------------------------------------------------------- /packages/content/src/utilities/format-time/format-time.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import advancedFormat from 'dayjs/plugin/advancedFormat'; 3 | import utc from 'dayjs/plugin/utc'; 4 | import timezone from 'dayjs/plugin/timezone'; 5 | import { DEFAULT_TIMEZONE } from './constants'; 6 | 7 | dayjs.extend(advancedFormat); 8 | dayjs.extend(utc); 9 | dayjs.extend(timezone); 10 | 11 | /** 12 | * 13 | * @param date - string in ISO 8601 format 14 | * @param timeZone - optional time zone (fallback to default time zone if not set) 15 | * @param format - date display format 16 | * @returns formatted string 17 | */ 18 | const formatTime = (date: string, timeZone: string | undefined, format: string): string => { 19 | const timeZoneOrDefault = timeZone ?? DEFAULT_TIMEZONE; 20 | return dayjs(date).tz(timeZoneOrDefault).format(format); 21 | }; 22 | 23 | export default formatTime; 24 | -------------------------------------------------------------------------------- /packages/content/src/utilities/format-time/index.ts: -------------------------------------------------------------------------------- 1 | export { default as formatTime } from './format-time'; 2 | export * from './constants'; 3 | -------------------------------------------------------------------------------- /packages/content/src/utilities/hydrate-content/index.ts: -------------------------------------------------------------------------------- 1 | export { default as hydrateContent } from './hydrate-content'; 2 | export { default as courseRuns } from './course-run'; 3 | 4 | export type { AvailabilityStatus, ContentItem, HydratedContentItem } from './types'; 5 | -------------------------------------------------------------------------------- /packages/content/src/utilities/hydrate-content/types.ts: -------------------------------------------------------------------------------- 1 | import { Content } from '../../graphql/global-types'; 2 | 3 | export enum AvailabilityStatus { 4 | Completed = 'completed', 5 | Available = 'available', 6 | Started = 'started', 7 | NotStarted = 'not-started', 8 | NotCompleted = 'not-completed' 9 | } 10 | 11 | export type ContentItem = Content; 12 | 13 | export type HydratedContentItem = ContentItem & { 14 | hasUnmetPrerequisites: boolean; 15 | isActive: boolean; 16 | hasAvailability: boolean; 17 | isCompleted: boolean; 18 | isAvailable: boolean; 19 | isStarted: boolean; 20 | isNotStarted: boolean; 21 | isNotCompleted: boolean; 22 | kindIsScormOrXApi: boolean; 23 | locationIsOnline: boolean; 24 | locationIsInPerson: boolean; 25 | usesContentAccessText: boolean; 26 | callToAction: string; 27 | href: string; 28 | priceInCents?: number; 29 | suggestedRetailPriceInCents?: number; 30 | displayCourse?: string; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/content/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './format-time'; 2 | export * from './hydrate-content'; 3 | -------------------------------------------------------------------------------- /packages/dashboard-stats/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/dashboard-stats` 2 | 3 | > The Dashboard Stats component displays a high level view of how many courses the user has access to, as well as how many threads/comments ("collaborations") they have participated in, alongside how many certificates they have earned. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { DashboardStats } from '@thoughtindustries/dashboard-stats'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/dashboard-stats/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "https://home.ti.test/helium" 3 | # # use the local copy of schema introspected from remote schema 4 | # schema: "graphql.schema.json" 5 | documents: "src/**/*.graphql" 6 | config: 7 | avoidOptionals: false 8 | maybeValue: T 9 | inlineFragmentTypes: 'combine' 10 | scalars: 11 | Slug: 'string' 12 | Date: 'string' 13 | JSON: 'any' 14 | AbsoluteOrRelativeURL: 'string' 15 | HexColor: 'string' 16 | RelativeURL: 'string' 17 | URL: 'string' 18 | UUID: 'string' 19 | strictScalars: true 20 | omitObjectTypes: true 21 | preResolveTypes: true 22 | generates: 23 | # use this to re-generate global types from schema 24 | src/graphql/global-types.ts: 25 | plugins: 26 | - "@thoughtindustries/graphql-codegen-plugin" 27 | src/: 28 | preset: near-operation-file 29 | presetConfig: 30 | # use a reduced global types scoped for content 31 | baseTypesPath: graphql/global-types.ts 32 | extension: .generated.tsx 33 | plugins: 34 | - "typescript-operations" 35 | - "typescript-react-apollo" 36 | # # use this to re-generate schema from remote schema 37 | # ./graphql.schema.json: 38 | # plugins: 39 | # - "introspection" 40 | -------------------------------------------------------------------------------- /packages/dashboard-stats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/dashboard-stats", 3 | "version": "1.2.5", 4 | "description": "User's dashboard stats", 5 | "author": "Blake Reimer ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": { 20 | "generate": "graphql-codegen --config codegen.yml" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/thoughtindustries/helium/issues" 24 | }, 25 | "dependencies": { 26 | "@apollo/client": "^3.7.0", 27 | "@thoughtindustries/content": "^1.2.5", 28 | "graphql": "^16.6.0", 29 | "react-i18next": "^11.18.6" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import DashboardStats from './dashboard-stats'; 2 | 3 | export * from './types'; 4 | export { DashboardStats }; 5 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/components/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | export interface DashboardStatsProps { 3 | certificatesCount: number; 4 | collaborationsCount: number; 5 | availableCoursesCount: number; 6 | startedCoursesCount: number; 7 | completedCoursesCount: number; 8 | } 9 | 10 | export interface StatsProps { 11 | label: string; 12 | stat: number; 13 | color: string; 14 | icon: ReactNode; 15 | } 16 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/graphql/UserStats.graphql: -------------------------------------------------------------------------------- 1 | query UserStats { 2 | CurrentUser { 3 | certificatesCount 4 | collaborationsCount 5 | availableCoursesCount 6 | startedCoursesCount 7 | completedCoursesCount 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/graphql/global-types.ts: -------------------------------------------------------------------------------- 1 | export type Maybe = T; 2 | export type InputMaybe = T; 3 | export type Exact = { [K in keyof T]: T[K] }; 4 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; 5 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; 6 | /** All built-in and custom scalars, mapped to their actual values */ 7 | export type Scalars = { 8 | ID: string; 9 | String: string; 10 | Boolean: boolean; 11 | Int: number; 12 | Float: number; 13 | AbsoluteOrRelativeURL: string; 14 | Date: string; 15 | HexColor: string; 16 | JSON: any; 17 | RelativeURL: string; 18 | Slug: string; 19 | URL: string; 20 | UUID: string; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UserStats.generated'; 2 | -------------------------------------------------------------------------------- /packages/dashboard-stats/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './graphql'; 2 | export * from './components'; 3 | -------------------------------------------------------------------------------- /packages/dashboard-stats/stories/DashboardStats.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DashboardStats, UserStatsDocument } from '../src'; 3 | import { StoryObj, Meta } from '@storybook/react'; 4 | 5 | const meta: Meta = { 6 | component: DashboardStats, 7 | title: 'Packages/Dashboard Stats' 8 | }; 9 | 10 | export default meta; 11 | type Dashboard = StoryObj; 12 | 13 | export const Dashboard: Dashboard = { 14 | render: () => , 15 | parameters: { 16 | apolloClient: { 17 | mocks: [ 18 | { 19 | request: { 20 | query: UserStatsDocument 21 | }, 22 | result: { 23 | data: { 24 | CurrentUser: { 25 | availableCoursesCount: 1200, 26 | startedCoursesCount: 5, 27 | completedCoursesCount: 10, 28 | certificatesCount: 3, 29 | collaborationsCount: 1 30 | } 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /packages/featured-content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/featured-content", 3 | "version": "1.3.5", 4 | "description": "A base component for featured content", 5 | "author": "Rob Schillinger ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "@apollo/client": "^3.7.0", 25 | "@thoughtindustries/content": "^1.2.5", 26 | "@thoughtindustries/header": "^1.1.2", 27 | "@thoughtindustries/hooks": "^1.2.2", 28 | "clsx": "^1.1.1", 29 | "graphql": "^16.6.0", 30 | "i18next": "^21.10.0", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "react-i18next": "^11.18.6" 34 | }, 35 | "peerDependencies": { 36 | "react": "^17 || ^18", 37 | "react-dom": "^17 || ^18" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/featured-content/src/featured-content.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SidebarPosition, FeaturedContentProps } from './types'; 3 | 4 | const FeaturedContent = ({ 5 | sidebar, 6 | sidebarPosition, 7 | children 8 | }: FeaturedContentProps): JSX.Element => { 9 | const wrappedSidebar = sidebar &&
{sidebar}
; 10 | const wrappedChildren = ( 11 |
{children}
12 | ); 13 | return ( 14 |
15 |
16 | {sidebarPosition === SidebarPosition.Left && wrappedSidebar} 17 | {wrappedChildren} 18 | {sidebarPosition === SidebarPosition.Right && wrappedSidebar} 19 |
20 |
21 | ); 22 | }; 23 | 24 | FeaturedContent.displayName = 'FeaturedContent'; 25 | 26 | export default FeaturedContent; 27 | -------------------------------------------------------------------------------- /packages/featured-content/src/index.ts: -------------------------------------------------------------------------------- 1 | import FeaturedContent from './featured-content'; 2 | import { SidebarRss, SidebarDefault } from './variants/sidebar'; 3 | import { 4 | ContentTileStandardLayout, 5 | ContentTileDescriptiveLayout, 6 | ContentMultiCarousel, 7 | ContentCarousel, 8 | ContentTileImageOverlay 9 | } from './variants/content'; 10 | 11 | export * from './types'; 12 | export { 13 | FeaturedContent, 14 | SidebarRss, 15 | SidebarDefault, 16 | ContentTileStandardLayout, 17 | ContentTileDescriptiveLayout, 18 | ContentMultiCarousel, 19 | ContentCarousel, 20 | ContentTileImageOverlay 21 | }; 22 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/content/index.ts: -------------------------------------------------------------------------------- 1 | import ContentTileStandardLayout from './tile-standard-layout'; 2 | import ContentTileDescriptiveLayout from './tile-descriptive-layout'; 3 | import ContentMultiCarousel from './multi-carousel'; 4 | import ContentCarousel from './carousel'; 5 | import ContentTileImageOverlay from './tile-image-overlay'; 6 | 7 | export { 8 | ContentTileStandardLayout, 9 | ContentTileDescriptiveLayout, 10 | ContentMultiCarousel, 11 | ContentCarousel, 12 | ContentTileImageOverlay 13 | }; 14 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/content/item-asset-block.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | 4 | interface ItemAssetBlockProps { 5 | asset?: string; 6 | classNames?: string; 7 | } 8 | 9 | const ItemAssetBlock = ({ asset, classNames = '' }: ItemAssetBlockProps): JSX.Element => ( 10 | 17 | ); 18 | 19 | ItemAssetBlock.displayName = 'ItemAssetBlock'; 20 | 21 | export default ItemAssetBlock; 22 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/content/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * in tailwind JIT mode, dynamic values like `md:grid-cols-${count}` are not supported. 3 | * use static complete strings instead. 4 | * @param desktopColumnCount 5 | * @returns 6 | */ 7 | export const tileClassnameByDesktopColumnCount = (desktopColumnCount: number): string => { 8 | switch (desktopColumnCount) { 9 | case 2: 10 | return 'md:grid-cols-2'; 11 | case 3: 12 | return 'md:grid-cols-3'; 13 | case 4: 14 | return 'md:grid-cols-4'; 15 | case 5: 16 | return 'md:grid-cols-5'; 17 | } 18 | return ''; 19 | }; 20 | 21 | export const limitText = (text: string, maxLength: number): string => { 22 | if (text.length > maxLength) { 23 | return `${text.substring(0, maxLength)}...`; 24 | } 25 | return text; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/content/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { Header } from '@thoughtindustries/header'; 3 | import { HeaderOptions } from '../../types'; 4 | 5 | interface FeaturedContentContentWrapperProps { 6 | headerOptions: HeaderOptions; 7 | children: ReactNode; 8 | } 9 | 10 | const ContentWrapper = ({ 11 | headerOptions, 12 | children 13 | }: FeaturedContentContentWrapperProps): JSX.Element => { 14 | const { title, ...restHeaderProps } = headerOptions; 15 | return ( 16 | <> 17 | {title &&
} 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | ContentWrapper.displayName = 'ContentWrapper'; 24 | 25 | export default ContentWrapper; 26 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/sidebar/default.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FeaturedContentSidebarBaseProps } from '../../types'; 3 | import SidebarWrapper from './wrapper'; 4 | 5 | const SidebarDefault = ({ title, children }: FeaturedContentSidebarBaseProps): JSX.Element => ( 6 | {children} 7 | ); 8 | 9 | SidebarDefault.displayName = 'SidebarDefault'; 10 | 11 | export default SidebarDefault; 12 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | import SidebarRss from './rss'; 2 | import SidebarDefault from './default'; 3 | 4 | export { SidebarRss, SidebarDefault }; 5 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/sidebar/rss.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { FeaturedContentSidebarRssProps } from '../../types'; 4 | import SidebarWrapper from './wrapper'; 5 | import { useRssItemsQuery } from '@thoughtindustries/content'; 6 | 7 | const SidebarRss = ({ title, feedUrl }: FeaturedContentSidebarRssProps): JSX.Element => { 8 | const { t } = useTranslation(); 9 | const { data, loading, error } = useRssItemsQuery({ 10 | variables: { feedUrl } 11 | }); 12 | let content; 13 | if (loading || error) { 14 | content =
{t('please-wait')}
; 15 | } else { 16 | content = ( 17 | <> 18 | {data && 19 | data.RssItems.map(({ title, link }, index) => ( 20 | 21 | {title} 22 | 23 | ))} 24 | 25 | ); 26 | } 27 | 28 | return {content}; 29 | }; 30 | 31 | SidebarRss.displayName = 'SidebarRss'; 32 | 33 | export default SidebarRss; 34 | -------------------------------------------------------------------------------- /packages/featured-content/src/variants/sidebar/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Header } from '@thoughtindustries/header'; 3 | import { FeaturedContentSidebarBaseProps } from '../../types'; 4 | 5 | type FeaturedContentSidebarWrapperProps = FeaturedContentSidebarBaseProps; 6 | 7 | const SidebarWrapper = ({ title, children }: FeaturedContentSidebarWrapperProps): JSX.Element => ( 8 |
9 | {title &&
} 10 |
{children}
11 |
12 | ); 13 | 14 | SidebarWrapper.displayName = 'SidebarWrapper'; 15 | 16 | export default SidebarWrapper; 17 | -------------------------------------------------------------------------------- /packages/header/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/header` 2 | 3 | > The Header displays a user-provided title, commonly used above another component. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { Header } from '@thoughtindustries/header'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 |
15 | ``` 16 | -------------------------------------------------------------------------------- /packages/header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/header", 3 | "version": "1.1.2", 4 | "description": "A base component for header", 5 | "author": "Rob Schillinger ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0" 26 | }, 27 | "peerDependencies": { 28 | "react": "^17 || ^18", 29 | "react-dom": "^17 || ^18" 30 | }, 31 | "gitHead": "8e33358778915b9915591931d98e63a020525072" 32 | } 33 | -------------------------------------------------------------------------------- /packages/header/src/index.ts: -------------------------------------------------------------------------------- 1 | import Header from './header'; 2 | 3 | export * from './types'; 4 | export { Header }; 5 | -------------------------------------------------------------------------------- /packages/header/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface HeaderProps { 2 | /** title that appears on the header */ 3 | title: string; 4 | /** display alternate title */ 5 | alternateTitleDisplay?: boolean; 6 | /** open link in new tab */ 7 | linkOpenInNewTab?: boolean; 8 | /** url for link that appears next to the title */ 9 | linkUrl?: string; 10 | /** text for link that appears next to the title */ 11 | linkText?: string; 12 | } 13 | -------------------------------------------------------------------------------- /packages/header/stories/Header.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | import React from 'react'; 3 | import { HeaderProps, Header } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: Header, 7 | title: 'Packages/Header' 8 | }; 9 | 10 | export default meta; 11 | type Header = StoryObj; 12 | 13 | export const Base: Header = { 14 | render: args =>
, 15 | args: { 16 | title: 'Dolor Nullam Mattis Sem' 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/hero/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/hero` 2 | 3 | > A hero image is a large web banner image, prominently placed on a web page, generally in the front and center. It often consists of image and text. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { Hero } from '@thoughtindustries/hero'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/hero/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/hero", 3 | "version": "1.1.2", 4 | "description": "A base component for a hero image", 5 | "author": "Rob Schillinger ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0" 26 | }, 27 | "peerDependencies": { 28 | "react": "^17 || ^18", 29 | "react-dom": "^17 || ^18" 30 | }, 31 | "gitHead": "8e33358778915b9915591931d98e63a020525072" 32 | } 33 | -------------------------------------------------------------------------------- /packages/hero/src/index.ts: -------------------------------------------------------------------------------- 1 | import Hero from './hero'; 2 | 3 | export * from './types'; 4 | export { Hero }; 5 | -------------------------------------------------------------------------------- /packages/hero/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface HeroProps { 2 | /** title that appears on the hero image */ 3 | title?: string; 4 | /** subtitle that appears on the hero image */ 5 | subtitle?: string; 6 | /** link text that appears on the hero image */ 7 | linkText?: string; 8 | /** open link in new tab for link text */ 9 | linkOpenInNewTab?: boolean; 10 | /** link url for link text */ 11 | linkUrl?: string; 12 | /** default asset for hero image */ 13 | asset?: string; 14 | /** optional asset for hero image in large screen */ 15 | largeAsset?: string; 16 | /** optional asset for hero image in small screen */ 17 | smallAsset?: string; 18 | } 19 | -------------------------------------------------------------------------------- /packages/hero/stories/Hero.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | import React from 'react'; 3 | import { HeroProps, Hero } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: Hero, 7 | title: 'Packages/Hero' 8 | }; 9 | 10 | export default meta; 11 | type Hero = StoryObj; 12 | 13 | export const Base: Hero = { 14 | render: args => , 15 | args: { 16 | title: 'Dolor Nullam Mattis Sem', 17 | subtitle: 18 | 'Maecenas sed diam eget risus varius blandit sit amet non magna. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.', 19 | asset: 20 | 'https://d36ai2hkxl16us.cloudfront.net/thoughtindustries/image/upload/a_exif,c_fill,w_800/v1416438573/placeholder_kcjvxm.jpg' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/hooks/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/hooks` 2 | 3 | > A collection of React Hooks for @thoughtindustries/helium UI components. 4 | 5 | ## Import hooks 6 | 7 | ``` 8 | import { useMedia, useSdk } from '@thoughtindustries/hooks'; 9 | ``` 10 | -------------------------------------------------------------------------------- /packages/hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/hooks", 3 | "version": "1.2.2", 4 | "description": "Common hooks shared throughout @thoughtindustries/helium packages", 5 | "author": "Lu Jiang ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "@use-gesture/react": "^10.2.24", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0" 27 | }, 28 | "peerDependencies": { 29 | "react": "^17 || ^18", 30 | "react-dom": "^17 || ^18" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | import { default as useCarouselBehavior } from './use-carousel-behavior'; 2 | import { default as useMedia } from './use-media'; 3 | import { default as useMultiCarouselBehavior } from './use-multi-carousel-behavior'; 4 | import { default as useOnClickOutside } from './use-on-click-outside'; 5 | import { default as usePrevious } from './use-previous'; 6 | import { default as useScreenSize, ScreenSize } from './use-screen-size'; 7 | import { default as useSdk } from './use-sdk'; 8 | import { default as useWindowEventListener } from './use-window-event-listener'; 9 | 10 | export { 11 | ScreenSize, 12 | useCarouselBehavior, 13 | useMedia, 14 | useMultiCarouselBehavior, 15 | useOnClickOutside, 16 | usePrevious, 17 | useScreenSize, 18 | useSdk, 19 | useWindowEventListener 20 | }; 21 | -------------------------------------------------------------------------------- /packages/hooks/src/use-previous.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useEffect, useRef } from 'react'; 2 | 3 | /** 4 | * Reference: https://usehooks.com/usePrevious/ 5 | * @param value Value 6 | * @returns Previous value if applied 7 | */ 8 | export default function usePrevious(value: T): T | undefined { 9 | // The ref object is a generic container whose current property is mutable ... 10 | // ... and can hold any value, similar to an instance property on a class 11 | const ref: MutableRefObject = useRef(); 12 | // Store current value in ref 13 | useEffect(() => { 14 | ref.current = value; 15 | }, [value]); // Only re-run if value changes 16 | // Return previous value (happens before update in useEffect above) 17 | return ref.current; 18 | } 19 | -------------------------------------------------------------------------------- /packages/hooks/src/use-screen-size.ts: -------------------------------------------------------------------------------- 1 | import useMedia from './use-media'; 2 | 3 | export enum ScreenSize { 4 | XXXLarge = 'xxxlarge', 5 | XXLarge = 'xxlarge', 6 | XLarge = 'xlarge', 7 | Large = 'large', 8 | Medium = 'medium', 9 | Small = 'small' 10 | } 11 | 12 | // map screen size to media query (smallest range or largest screen on top) 13 | type ScreenSizeToMediaQueryMap = { [key in ScreenSize]?: string }; 14 | const screenSizeToMediaQueryMap: ScreenSizeToMediaQueryMap = { 15 | [ScreenSize.XXXLarge]: '(min-width: 160.063em)', 16 | [ScreenSize.XXLarge]: '(min-width: 120.063em)', 17 | [ScreenSize.XLarge]: '(min-width: 90.063em)', 18 | [ScreenSize.Large]: '(min-width: 64.063em)', 19 | [ScreenSize.Medium]: '(min-width: 48.063em)' 20 | }; 21 | 22 | export default function useScreenSize(): ScreenSize { 23 | const queries = Object.values(screenSizeToMediaQueryMap); 24 | const values = Object.keys(screenSizeToMediaQueryMap) as ScreenSize[]; 25 | const screenSize = useMedia(queries, values, ScreenSize.Small); 26 | return screenSize; 27 | } 28 | -------------------------------------------------------------------------------- /packages/link-lists/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/link-lists` 2 | 3 | > The Link Lists component is a nifty option for footers or when you just need to offer a cluster of navigation options. Choose from a variety of multi-column configurations for the one perfectly suited to your needs. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { LinkLists, LinkList } from '@thoughtindustries/link-lists'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | # Base 15 | 16 | 17 | List subcategory 1 18 | 19 | 20 | 21 | # With display cutoff 22 | 23 | 24 | List subcategory 1 25 | List subcategory 2 26 | 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/link-lists/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/link-lists", 3 | "version": "1.1.2", 4 | "description": "A base component for link lists", 5 | "author": "Rob Schillinger ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "@thoughtindustries/header": "^1.1.2", 25 | "i18next": "^21.10.0", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "react-i18next": "^11.18.6" 29 | }, 30 | "peerDependencies": { 31 | "react": "^17 || ^18", 32 | "react-dom": "^17 || ^18" 33 | }, 34 | "gitHead": "8e33358778915b9915591931d98e63a020525072" 35 | } 36 | -------------------------------------------------------------------------------- /packages/link-lists/src/index.ts: -------------------------------------------------------------------------------- 1 | import LinkLists from './link-lists'; 2 | import LinkList from './link-list'; 3 | 4 | export * from './types'; 5 | export { LinkLists, LinkList }; 6 | -------------------------------------------------------------------------------- /packages/link-lists/src/link-lists.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Header } from '@thoughtindustries/header'; 3 | import { LinkListsProps } from './types'; 4 | 5 | const LinkLists = (props: LinkListsProps): JSX.Element => { 6 | const { title, alternateTitleDisplay, children } = props; 7 | 8 | return ( 9 |
10 |
11 | {title &&
} 12 |
    {children}
13 |
14 |
15 | ); 16 | }; 17 | 18 | LinkLists.displayName = 'LinkLists'; 19 | 20 | export default LinkLists; 21 | -------------------------------------------------------------------------------- /packages/navigation-bar/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/navigation-bar` 2 | 3 | > The Navigation Bar widget is a simple way to add standard horizontal navigation to any page. To share even more options with learners, add a drop-down sub-menu. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { NavigationBar, NavigationBarLink } from '@thoughtindustries/navigation-bar'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/navigation-bar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/navigation-bar", 3 | "version": "1.2.2", 4 | "description": "A base component for navigation bar", 5 | "author": "Lu Jiang ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "@thoughtindustries/hooks": "^1.2.2", 25 | "clsx": "^1.1.1", 26 | "i18next": "^21.10.0", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-i18next": "^11.18.6" 30 | }, 31 | "peerDependencies": { 32 | "react": "^17 || ^18", 33 | "react-dom": "^17 || ^18" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/navigation-bar/src/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { NavigationBarContextType } from './types'; 3 | 4 | const NavigationBarContext = createContext(undefined); 5 | 6 | export default NavigationBarContext; 7 | -------------------------------------------------------------------------------- /packages/navigation-bar/src/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const MenuIcon = (): JSX.Element => ( 4 | 11 | 12 | 13 | ); 14 | 15 | export const CaretDownIcon = (): JSX.Element => ( 16 | 17 | 18 | 19 | ); 20 | 21 | export const CaretRightIcon = (): JSX.Element => ( 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /packages/navigation-bar/src/index.ts: -------------------------------------------------------------------------------- 1 | import NavigationBar from './navigation-bar'; 2 | import NavigationBarLink from './navigation-bar-link'; 3 | 4 | export * from './types'; 5 | export { NavigationBar, NavigationBarLink }; 6 | -------------------------------------------------------------------------------- /packages/navigation-bar/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | 3 | export interface NavigationBarProps { 4 | children: ReactElement | ReactElement[]; 5 | } 6 | 7 | interface LinkBaseProps { 8 | /** label for navigation bar link */ 9 | label: string; 10 | /** url for link */ 11 | href?: string; 12 | /** open link in new tab for link */ 13 | linkOpenInNewTab?: boolean; 14 | } 15 | 16 | export interface NavigationBarLinkProps extends LinkBaseProps { 17 | /** index of menu link */ 18 | index?: number; 19 | /** list of sub links */ 20 | children?: ReactElement | ReactElement[]; 21 | } 22 | 23 | export interface NavigationBarLinkSubLinkProps extends LinkBaseProps { 24 | /** url for link */ 25 | href: string; 26 | } 27 | 28 | export type NavigationBarContextType = { 29 | navigate: (direction: number, menuId?: string) => void; 30 | isSmallScreen: boolean; 31 | currentMenuId?: string; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/navigation-bar/src/use-navigation-bar.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NavigationBarContext from './context'; 3 | 4 | export default function useNavigationBar() { 5 | const context = React.useContext(NavigationBarContext); 6 | 7 | if (!context) { 8 | throw new Error('Expected a Navigation Bar Context, but no such Context was found'); 9 | } 10 | 11 | return context; 12 | } 13 | -------------------------------------------------------------------------------- /packages/navigation-bar/stories/NavigationBar.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StoryObj, Meta } from '@storybook/react'; 3 | import { NavigationBar, NavigationBarLink, NavigationBarProps } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: NavigationBar, 7 | title: 'Packages/Navigation Bar' 8 | }; 9 | 10 | export default meta; 11 | type NavigationBar = StoryObj; 12 | 13 | export const Base: NavigationBar = { 14 | render: () => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | }; 29 | -------------------------------------------------------------------------------- /packages/pagination/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/pagination` 2 | 3 | > This package is used to display pagination controls on components such as the catalog. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { Pagination } from '@thoughtindustries/pagination'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | `/?page=${page}`} /> 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/pagination/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/pagination", 3 | "version": "1.2.3", 4 | "description": "A base component for pagination", 5 | "author": "Lu Jiang ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "react": "^17 || ^18", 25 | "react-dom": "^17 || ^18", 26 | "tailwind-merge": "^1.14.0" 27 | }, 28 | "peerDependencies": { 29 | "react": "^17 || ^18", 30 | "react-dom": "^17 || ^18" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/pagination/src/constants.ts: -------------------------------------------------------------------------------- 1 | // number of pages on either side of active 2 | export const PAGINATION_INNER_WINDOW = 3; 3 | export const PAGINATION_MAX_PAGES = PAGINATION_INNER_WINDOW * 2 + 1; 4 | 5 | export const DEFAULT_PAGE = 1; 6 | 7 | export const DEFAULT_PAGE_SIZE = 50; 8 | 9 | export const DEFAULT_HIDE_PAGE_LIST = false; 10 | -------------------------------------------------------------------------------- /packages/pagination/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export type { PaginationProps } from './types'; 3 | export { default as Pagination } from './pagination'; 4 | -------------------------------------------------------------------------------- /packages/pagination/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ElementType } from 'react'; 2 | 3 | export type VisiblePage = { 4 | number: number; 5 | label: number | string; 6 | isActive?: boolean; 7 | isFirst?: boolean; 8 | isLast?: boolean; 9 | }; 10 | 11 | export type DisplayedPageRange = { 12 | start: number; 13 | end: number; 14 | }; 15 | 16 | export interface PaginationProps { 17 | /** Optional current page number */ 18 | page?: number; 19 | /** Total items count */ 20 | total: number; 21 | /** Optional page size */ 22 | pageSize?: number; 23 | /** Function to get link for a given page */ 24 | getPageLink: (page: number) => string; 25 | /** Optional setting to hide page list */ 26 | hidePageList?: boolean; 27 | /** Optional link component to override default HTMLAnchorElement */ 28 | linkComponent?: ElementType; 29 | } 30 | -------------------------------------------------------------------------------- /packages/pagination/stories/Pagination.stories.tsx: -------------------------------------------------------------------------------- 1 | import { StoryObj, Meta } from '@storybook/react'; 2 | import React from 'react'; 3 | import { Pagination, PaginationProps } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: Pagination, 7 | title: 'Packages/Pagination' 8 | }; 9 | 10 | export default meta; 11 | type Pagination = StoryObj; 12 | 13 | const getPageLink: PaginationProps['getPageLink'] = page => `/sample?page=${page}`; 14 | 15 | export const Base: Pagination = { 16 | render: args => , 17 | args: { 18 | total: 100, 19 | getPageLink 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /packages/testimonial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/testimonial", 3 | "version": "1.2.2", 4 | "description": "A base component for testimonials", 5 | "author": "Blake Reimer ", 6 | "homepage": "https://github.com/thoughtindustries/helium#readme", 7 | "license": "MIT", 8 | "main": "src/index.ts", 9 | "files": [ 10 | "src" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "scripts": {}, 20 | "bugs": { 21 | "url": "https://github.com/thoughtindustries/helium/issues" 22 | }, 23 | "dependencies": { 24 | "@thoughtindustries/hooks": "^1.2.2", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0" 27 | }, 28 | "peerDependencies": { 29 | "react": "^17 || ^18", 30 | "react-dom": "^17 || ^18" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/testimonial/src/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const IconLeft = (): JSX.Element => ( 4 | 11 | 12 | 13 | ); 14 | 15 | export const IconRight = (): JSX.Element => ( 16 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /packages/testimonial/src/index.ts: -------------------------------------------------------------------------------- 1 | import TestimonialMultiCarousel from './multi-carousel'; 2 | 3 | export * from './types'; 4 | export { TestimonialMultiCarousel }; 5 | -------------------------------------------------------------------------------- /packages/testimonial/src/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | export interface TestimonialProps { 3 | /** children */ 4 | children: ReactNode; 5 | } 6 | 7 | export interface TestimonialCarouselProps { 8 | /** children */ 9 | children: ReactNode; 10 | /** row item count in desktop view */ 11 | desktopColumnCount: number; 12 | /** Testimonial text color */ 13 | textColor?: string; 14 | } 15 | 16 | export interface TestimonialItemProps { 17 | /** user's quote */ 18 | quote: string; 19 | /** user's name */ 20 | username: string; 21 | /** user's description */ 22 | description: string; 23 | /** Testimonial background color */ 24 | backgroundColor: string; 25 | /** Testimonial text color */ 26 | textColor: string; 27 | /** Testimonial alignment */ 28 | alignment: string; 29 | /** Image */ 30 | asset: string; 31 | } 32 | 33 | export interface TestimonialMultiCarouselContextType { 34 | /** row item count in desktop view */ 35 | desktopColumnCount: number; 36 | } 37 | -------------------------------------------------------------------------------- /packages/testimonial/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * in tailwind JIT mode, dynamic values like `md:grid-cols-${count}` are not supported. 3 | * use static complete strings instead. 4 | * @param desktopColumnCount 5 | * @returns 6 | */ 7 | export const tileClassnameByDesktopColumnCount = (desktopColumnCount: number): string => { 8 | switch (desktopColumnCount) { 9 | case 2: 10 | return 'md:grid-cols-2'; 11 | case 3: 12 | return 'md:grid-cols-3'; 13 | case 4: 14 | return 'md:grid-cols-4'; 15 | case 5: 16 | return 'md:grid-cols-5'; 17 | } 18 | return ''; 19 | }; 20 | 21 | export const limitText = (text: string, maxLength: number): string => { 22 | if (text.length > maxLength) { 23 | return `${text.substring(0, maxLength)}...`; 24 | } 25 | return text; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/user/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/user` 2 | 3 | > A base component for user login and registration. 4 | 5 | ## Login 6 | 7 | The `Login` component renders a form for learners to enter email address and password to sign in, as well as a link to retrieve password in case the learner forgets their password. Once learner signs in successfully, learner will be redirected to the dashboard page. 8 | 9 | ### Example code 10 | 11 | ```tsx 12 | import { Login } from '@thoughtindustries/user'; 13 | 14 | export function MyComponent() { 15 | // ... 16 | 17 | return ( 18 | 19 | ); 20 | } 21 | ``` 22 | 23 | ## Registration 24 | 25 | The `Registration` component renders a form for learners to register for your site and gain access to the learner dashboard. 26 | -------------------------------------------------------------------------------- /packages/user/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: https://home.ti.test/helium 3 | documents: src/**/*.graphql 4 | config: 5 | avoidOptionals: false 6 | maybeValue: T 7 | inlineFragmentTypes: combine 8 | scalars: 9 | Slug: string 10 | Date: string 11 | JSON: any 12 | AbsoluteOrRelativeURL: string 13 | HexColor: string 14 | RelativeURL: string 15 | URL: string 16 | UUID: string 17 | strictScalars: true 18 | omitObjectTypes: false 19 | preResolveTypes: true 20 | generates: 21 | # use this to re-generate global types from schema 22 | src/graphql/global-types.ts: 23 | plugins: 24 | - '@thoughtindustries/graphql-codegen-plugin' 25 | src/: 26 | preset: near-operation-file 27 | presetConfig: 28 | # use a reduced global types scoped for content 29 | baseTypesPath: graphql/global-types.ts 30 | extension: .generated.tsx 31 | plugins: 32 | - typescript-operations 33 | - typescript-react-apollo 34 | -------------------------------------------------------------------------------- /packages/user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/user", 3 | "version": "1.2.3", 4 | "description": "A base component for user login and registration", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "generate": "graphql-codegen --config codegen.yml" 8 | }, 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/thoughtindustries/helium.git" 15 | }, 16 | "author": "Blake Reimer ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/thoughtindustries/helium/issues" 20 | }, 21 | "homepage": "https://github.com/thoughtindustries/helium#readme", 22 | "dependencies": { 23 | "@apollo/client": "^3.7.0", 24 | "@graphql-codegen/introspection": "^2.2.0", 25 | "@types/lodash": "^4.14.182", 26 | "graphql": "^16.6.0", 27 | "html-react-parser": "^3.0.1", 28 | "i18next": "^21.10.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/user/src/components/banner/banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ValidationProps } from '../types'; 3 | 4 | const Banner = ({ valid, message }: ValidationProps): JSX.Element => { 5 | return ( 6 | <> 7 | {valid !== null && valid !== undefined && message !== null && message !== undefined ? ( 8 | valid ? ( 9 |
10 | {message} 11 |
12 | ) : ( 13 |
14 | {message} 15 |
16 | ) 17 | ) : null} 18 | 19 | ); 20 | }; 21 | 22 | Banner.displayName = 'Banner'; 23 | export default Banner; 24 | -------------------------------------------------------------------------------- /packages/user/src/components/banner/index.ts: -------------------------------------------------------------------------------- 1 | import Banner from './banner'; 2 | 3 | export { Banner }; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const CloseIcon = () => ( 4 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /packages/user/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './registration'; 2 | export * from './login'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/login/index.tsx: -------------------------------------------------------------------------------- 1 | import Login from './login'; 2 | 3 | export { Login }; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/redemption/index.ts: -------------------------------------------------------------------------------- 1 | import Redemption from './redemption'; 2 | 3 | export { Redemption }; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/redemption/redemption.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import CodeBox from './code-box'; 4 | 5 | const Redemption = (): JSX.Element => { 6 | const { t } = useTranslation(); 7 | const [count, setCount] = useState(1); 8 | const list = Array.from(Array(count), (_, i) => ); 9 | 10 | return ( 11 |
12 | {list} 13 | 22 |
23 | ); 24 | }; 25 | 26 | Redemption.displayName = 'Redemption'; 27 | export default Redemption; 28 | -------------------------------------------------------------------------------- /packages/user/src/components/registration/index.ts: -------------------------------------------------------------------------------- 1 | import Registration from './registration'; 2 | 3 | export { Registration }; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/terms-conditions-modal/index.ts: -------------------------------------------------------------------------------- 1 | import TermsConditionsModal from './terms-conditions-modal'; 2 | 3 | export { TermsConditionsModal }; 4 | -------------------------------------------------------------------------------- /packages/user/src/components/terms-conditions/index.ts: -------------------------------------------------------------------------------- 1 | import TermsConditions from './terms-conditions'; 2 | 3 | export { TermsConditions }; 4 | -------------------------------------------------------------------------------- /packages/user/src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export * as GlobalTypes from './global-types'; 2 | 3 | export * from './mutations'; 4 | export * from './queries'; 5 | -------------------------------------------------------------------------------- /packages/user/src/graphql/mutations/Login.graphql: -------------------------------------------------------------------------------- 1 | mutation Login($email: String!, $password: String!) { 2 | Login(email: $email, password: $password) 3 | } 4 | -------------------------------------------------------------------------------- /packages/user/src/graphql/mutations/RedeemRegistrationAndRedemptionCodes.graphql: -------------------------------------------------------------------------------- 1 | mutation RedeemRegistrationAndRedemptionCodes($validatedRedemptionCodes: [String!]!) { 2 | RedeemRegistrationAndRedemptionCodes(validatedRedemptionCodes: $validatedRedemptionCodes) { 3 | redeemed 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/user/src/graphql/mutations/ValidateRedemptionCode.graphql: -------------------------------------------------------------------------------- 1 | mutation ValidateRedemptionCode($code: String!) { 2 | ValidateRedemptionCode(code: $code) { 3 | valid 4 | alreadyRedeemed 5 | codeExpired 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/user/src/graphql/mutations/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ValidateRedemptionCode.generated'; 2 | export * from './RedeemRegistrationAndRedemptionCodes.generated'; 3 | export * from './Login.generated'; 4 | -------------------------------------------------------------------------------- /packages/user/src/graphql/queries/TermsAndConditions.graphql: -------------------------------------------------------------------------------- 1 | query TermsAndConditions { 2 | TermsAndConditions { 3 | globalTerms 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/user/src/graphql/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TermsAndConditions.generated'; 2 | -------------------------------------------------------------------------------- /packages/user/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './graphql'; 3 | -------------------------------------------------------------------------------- /packages/video-player/README.md: -------------------------------------------------------------------------------- 1 | # `@thoughtindustries/video-player` 2 | 3 | > The Video Player plays an embedded video utilizing a Wistia asset. 4 | 5 | ## Import component 6 | 7 | ``` 8 | import { VideoPlayer } from '@thoughtindustries/video-player'; 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | 15 | ``` -------------------------------------------------------------------------------- /packages/video-player/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/video-player", 3 | "version": "1.1.5", 4 | "description": "", 5 | "main": "src/index.ts", 6 | "files": [ 7 | "src" 8 | ], 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/thoughtindustries/helium.git" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/thoughtindustries/helium/issues" 23 | }, 24 | "homepage": "https://github.com/thoughtindustries/helium#readme", 25 | "dependencies": { 26 | "@thoughtindustries/content": "^1.2.5", 27 | "@thoughtindustries/hooks": "^1.2.2", 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0" 30 | }, 31 | "peerDependencies": { 32 | "react": "^17 || ^18", 33 | "react-dom": "^17 || ^18" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/video-player/src/index.ts: -------------------------------------------------------------------------------- 1 | import VideoPlayer from './video-player'; 2 | 3 | export * from './types'; 4 | export { VideoPlayer }; 5 | -------------------------------------------------------------------------------- /packages/video-player/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface VideoPlayerProps { 2 | /** ID of Wistia Asset to be embedded */ 3 | asset: string; 4 | /** hex code of player color */ 5 | playerColor: string; 6 | /** id of user to associate with view */ 7 | userId?: string; 8 | /** controls whether the view is tracked */ 9 | doNotTrack?: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /packages/video-player/stories/VideoPlayer.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, StoryObj } from '@storybook/react'; 2 | import React from 'react'; 3 | import { VideoPlayer, VideoPlayerProps } from '../src'; 4 | 5 | const meta: Meta = { 6 | component: VideoPlayer, 7 | title: 'Packages/Video Player' 8 | }; 9 | 10 | export default meta; 11 | type VideoPlayer = StoryObj; 12 | 13 | export const Base: VideoPlayer = { 14 | render: args => , 15 | args: { 16 | asset: 'o8p3hzwdmj', 17 | playerColor: '#C1E1C1' 18 | } 19 | }; 20 | 21 | export const Alternate: VideoPlayer = { 22 | render: args => , 23 | args: { 24 | asset: '3ezt7m1tx1', 25 | playerColor: '#A7C7E7' 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('tailwindcss'), require('autoprefixer')] 3 | }; 4 | -------------------------------------------------------------------------------- /tooling/cli/__tests__/test-query-files/ContentFragment.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('@apollo/client'); 2 | 3 | const ContentFragmentTest = gql` 4 | fragment TestContentFragment on Content { 5 | asset 6 | authors 7 | availabilityStatus 8 | canAddToQueue 9 | contentTypeLabel 10 | courseGracePeriodEnded 11 | coursePresold 12 | courseStartDate 13 | currentUserMayReschedule 14 | currentUserUnmetCoursePrerequisites 15 | currentUserUnmetLearningPathPrerequisites 16 | description 17 | displayCourse 18 | kind 19 | hasChildren 20 | hideCourseDescription 21 | id 22 | isActive 23 | priceInCents 24 | rating 25 | ribbon { 26 | color 27 | contrastColor 28 | darkerColor 29 | label 30 | slug 31 | } 32 | slug 33 | source 34 | suggestedRetailPriceInCents 35 | title 36 | waitlistingEnabled 37 | waitlistingTriggered 38 | } 39 | `; 40 | 41 | module.exports = { ContentFragmentTest }; 42 | -------------------------------------------------------------------------------- /tooling/cli/__tests__/test-query-files/QueryNoTypename.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('@apollo/client'); 2 | 3 | // eslint-disable-next-line 4 | const CompanyTestQuery = gql` 5 | query CompanyDetailsTestQuery { 6 | CompanyDetails { 7 | name 8 | } 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /tooling/cli/__tests__/test-query-files/QueryWithFragment.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('@apollo/client'); 2 | const { ContentFragmentTest } = require('./ContentFragment'); 3 | // Borrowed from https://github.com/thoughtindustries/helium/blob/c1d6910f3d85f515e1a5345675145c9736fc3e27/packages/content/src/graphql/queries/CatalogQuery.generated.tsx#L22 4 | // eslint-disable-next-line 5 | const CatalogDocumentTest = gql` 6 | query CatalogTest($query: String, $querySignature: String, $querySort: String) { 7 | CatalogQuery(query: $query, querySignature: $querySignature, querySort: $querySort) { 8 | contentItems { 9 | ...TestContentFragment 10 | } 11 | } 12 | } 13 | ${ContentFragmentTest} 14 | `; 15 | -------------------------------------------------------------------------------- /tooling/cli/lib/helpers/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DEFAULT_GRAPHQL_SOURCE_PATHS: [ 3 | './pages/**/*.{js,jsx,ts,tsx}', 4 | './components/**/*.{js,jsx,ts,tsx}' 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /tooling/cli/lib/helpers/prompts.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | 3 | const promptQuestions = async questions => { 4 | return new Promise((resolve, reject) => { 5 | inquirer.prompt(questions).then(resolve).catch(reject); 6 | }); 7 | }; 8 | 9 | module.exports = { promptQuestions }; 10 | -------------------------------------------------------------------------------- /tooling/cli/lib/helpers/urls.js: -------------------------------------------------------------------------------- 1 | const instanceEndpoint = instance => { 2 | const { instanceUrl, apiKey } = instance; 3 | const base = new URL(instanceUrl); 4 | 5 | base.pathname = 'helium'; 6 | base.searchParams.set('apiKey', apiKey); 7 | 8 | return base.href; 9 | }; 10 | 11 | module.exports = { instanceEndpoint }; 12 | -------------------------------------------------------------------------------- /tooling/cli/lib/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const yargs = require('yargs'); 4 | const { hideBin } = require('yargs/helpers'); 5 | 6 | const createCli = argv => { 7 | const cli = yargs(hideBin(argv)).parserConfiguration({ 8 | 'boolean-negation': false 9 | }); 10 | 11 | cli 12 | .scriptName('helium') 13 | .usage(`Usage: $0 [options]`) 14 | .alias(`h`, `help`) 15 | .alias(`v`, `version`); 16 | 17 | return cli.commandDir('command-modules').wrap(cli.terminalWidth()).parse(); 18 | }; 19 | 20 | createCli(process.argv); 21 | -------------------------------------------------------------------------------- /tooling/cli/lib/prompts.js: -------------------------------------------------------------------------------- 1 | const { hasInput, isAbsoluteUrl, containsOnlyLetters, isEmail } = require('./validations'); 2 | 3 | const INSTANCE_QUESTIONS = [ 4 | { 5 | type: 'input', 6 | name: 'instanceUrl', 7 | message: 'What is the instance URL?', 8 | validate: isAbsoluteUrl 9 | }, 10 | { 11 | type: 'password', 12 | name: 'apiKey', 13 | message: 'What is the API Key for this instance?', 14 | validate: hasInput 15 | }, 16 | { 17 | type: 'input', 18 | name: 'nickname', 19 | message: 'What is the nickname for this instance?', 20 | validate: containsOnlyLetters 21 | }, 22 | { 23 | type: 'input', 24 | name: 'testUserEmail', 25 | message: 'What is the email of a test user in this instance?', 26 | validate: isEmail 27 | }, 28 | { 29 | type: 'confirm', 30 | name: 'addInstance', 31 | message: 'Would you like to add another instance?' 32 | } 33 | ]; 34 | 35 | const DEPLOYMENT_LOG_FETCH_QUESTION = { 36 | type: 'confirm', 37 | name: 'shouldFetchLogs', 38 | message: 'Would you like to fetch logs for the deployment?' 39 | }; 40 | 41 | module.exports = { 42 | INSTANCE_QUESTIONS, 43 | DEPLOYMENT_LOG_FETCH_QUESTION 44 | }; 45 | -------------------------------------------------------------------------------- /tooling/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/helium", 3 | "version": "2.2.0", 4 | "description": "The Helium CLI", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "helium": "lib/index.js" 11 | }, 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "dependencies": { 16 | "@graphql-tools/graphql-tag-pluck": "^7.1.3", 17 | "cross-env": "^7.0.3", 18 | "graphql": "^16.3.0", 19 | "i18next-scanner": "^3.1.0", 20 | "inquirer": "^8.1.2", 21 | "isomorphic-unfetch": "^3.1.0", 22 | "minimatch": "^9.0.3", 23 | "tar": "^6.1.11", 24 | "yargs": "^17.1.1" 25 | }, 26 | "devDependencies": { 27 | "@apollo/client": "^3.5.8" 28 | }, 29 | "engines": { 30 | "node": ">=16.18.1", 31 | "npm": ">=8.19.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tooling/create-helium-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | /template-base/** -------------------------------------------------------------------------------- /tooling/create-helium-app/README.md: -------------------------------------------------------------------------------- 1 | # create-helium-app 2 | 3 | Create a new [Helium](https://github.com/thoughtindustries/helium/tree/main/tooling/template_base) app: 4 | 5 | ```sh 6 | $ npm init helium-app 7 | ``` 8 | 9 | Or with yarn 10 | 11 | ```sh 12 | $ yarn create helium-app 13 | ``` 14 | -------------------------------------------------------------------------------- /tooling/create-helium-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-helium-app", 3 | "publishConfig": { 4 | "access": "public" 5 | }, 6 | "version": "1.1.35", 7 | "main": "index.js", 8 | "license": "MIT", 9 | "bin": { 10 | "create-helium": "index.js", 11 | "create-helium-app": "index.js" 12 | }, 13 | "files": [ 14 | "index.js", 15 | "scripts", 16 | "template-base", 17 | "template-base-essentials" 18 | ], 19 | "scripts": { 20 | "prepublishOnly": "node ./scripts/tmp-copy-template-from-tooling.js" 21 | }, 22 | "dependencies": { 23 | "inquirer": "^8.2.0", 24 | "kolorist": "^1.5.0", 25 | "minimist": "^1.2.5" 26 | }, 27 | "engines": { 28 | "node": ">=16.18.1", 29 | "npm": ">=8.19.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tooling/create-helium-app/scripts/tmp-copy-template-from-tooling.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a temporary script meant to copy `tooling/template-base` to `./template-base` 3 | * while we are actively developing Helium in 'tooling'. Eventually, the template will just 4 | * live in this folder. 5 | */ 6 | 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const { copy } = require('./utils'); 10 | 11 | const devPath = path.resolve(__dirname, '..', '..', '..', 'tooling', 'template-base'); 12 | const devEssentialsPath = path.resolve( 13 | __dirname, 14 | '..', 15 | '..', 16 | '..', 17 | 'tooling', 18 | 'template-base-essentials' 19 | ); 20 | const templatePath = path.resolve(__dirname, '..', './template-base'); 21 | const templateEssentialsPath = path.resolve(__dirname, '..', './template-base-essentials'); 22 | const skipFiles = ['node_modules', 'dist']; 23 | 24 | // Remove the symlink and replace it with a folder 25 | fs.unlinkSync(templatePath); 26 | fs.mkdirSync(templatePath, { recursive: true }); 27 | 28 | fs.unlinkSync(templateEssentialsPath); 29 | fs.mkdirSync(templateEssentialsPath, { recursive: true }); 30 | 31 | copy(devPath, templatePath, skipFiles); 32 | copy(devEssentialsPath, templateEssentialsPath, skipFiles); 33 | -------------------------------------------------------------------------------- /tooling/create-helium-app/scripts/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function copyDir(srcDir, destDir, skipFiles = []) { 5 | fs.mkdirSync(destDir, { recursive: true }); 6 | for (const file of fs.readdirSync(srcDir)) { 7 | const srcFile = path.resolve(srcDir, file); 8 | const destFile = path.resolve(destDir, file); 9 | copy(srcFile, destFile, skipFiles); 10 | } 11 | } 12 | 13 | function copy(src, dest, skipFiles = []) { 14 | if (skipFiles.some(file => src.includes(file))) return; 15 | 16 | const stat = fs.statSync(src); 17 | 18 | if (stat.isDirectory()) { 19 | copyDir(src, dest, skipFiles); 20 | } else { 21 | fs.copyFileSync(src, dest); 22 | } 23 | } 24 | 25 | module.exports = { 26 | copy 27 | }; 28 | -------------------------------------------------------------------------------- /tooling/create-helium-app/template-base: -------------------------------------------------------------------------------- 1 | ./../template-base -------------------------------------------------------------------------------- /tooling/create-helium-app/template-base-essentials: -------------------------------------------------------------------------------- 1 | ./../template-base-essentials -------------------------------------------------------------------------------- /tooling/helium-server/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist -------------------------------------------------------------------------------- /tooling/helium-server/src/clients/graphiql/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | min-height: 100vh; 5 | } 6 | #root { 7 | height: 100vh; 8 | } 9 | -------------------------------------------------------------------------------- /tooling/helium-server/src/clients/graphiql/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | GraphiQL 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tooling/helium-server/src/clients/graphiql/main.tsx: -------------------------------------------------------------------------------- 1 | import { createGraphiQLFetcher } from '@graphiql/toolkit'; 2 | import { GraphiQL } from 'graphiql'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | 6 | import 'graphiql/graphiql.css'; 7 | import './index.css'; 8 | 9 | const fetcher = createGraphiQLFetcher({ 10 | url: '/graphql' 11 | }); 12 | 13 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /tooling/helium-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { fetchUserAndAppearance, fetchUser } from './utilities/fetch-user-and-appearance'; 2 | import findTiInstance from './utilities/find-ti-instance'; 3 | import initPageContext from './utilities/init-page-context'; 4 | import tiConfig from './vite-config/vite.config'; 5 | import makeApolloClient from './utilities/make-apollo-client'; 6 | import setupHeliumServer from './server/server'; 7 | 8 | export { 9 | fetchUserAndAppearance, 10 | fetchUser, 11 | findTiInstance, 12 | initPageContext, 13 | setupHeliumServer, 14 | makeApolloClient, 15 | tiConfig 16 | }; 17 | -------------------------------------------------------------------------------- /tooling/helium-server/src/utilities/find-ti-instance.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | export default async function findTiInstance(instanceName: string) { 5 | const configPath = `${path.join(process.cwd(), 'ti-config.json')}`; 6 | const configJson = fs.readFileSync(configPath, 'utf8'); 7 | const { instances = [] } = JSON.parse(configJson); 8 | let instance = instances[0]; 9 | 10 | if (instanceName) { 11 | const possibleMatch = instances.find( 12 | (instance: Record) => instance.nickname === instanceName 13 | ); 14 | if (possibleMatch && possibleMatch.apiKey) { 15 | instance = possibleMatch; 16 | } 17 | } 18 | 19 | return instance; 20 | } 21 | -------------------------------------------------------------------------------- /tooling/helium-server/src/vite-config/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import ssr from 'vite-plugin-ssr/plugin'; 3 | 4 | const tiConfig: Record = { 5 | plugins: [react(), ssr()], 6 | optimizeDeps: { 7 | include: ['dayjs', 'universal-cookie'] 8 | }, 9 | envPrefix: 'HELIUM_PUBLIC_' 10 | }; 11 | 12 | export default tiConfig; 13 | -------------------------------------------------------------------------------- /tooling/helium-server/vite.graphiql.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | root: resolve(__dirname, 'src/clients/graphiql'), 7 | base: '/graphiql', 8 | build: { 9 | outDir: resolve(__dirname, 'dist/graphiql'), 10 | rollupOptions: { 11 | input: { 12 | index: resolve(__dirname, 'src/clients/graphiql/index.html') 13 | } 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/README.md: -------------------------------------------------------------------------------- 1 | # Custom storybook addon for apollo client 2 | 3 | This addon extends the existing addon `storybook-addon-apollo-client` to adapt to Storybook V7 pre-releases. 4 | 5 | ## Changes 6 | 7 | The current addon `storybook-addon-apollo-client` utilizes `useGlobals` to store mocked Apollo Provider queries for each applicable story. Starting in Storybook V7, it requires addons to register global variables with a default value. Otherwise, the settings of global variables will take no effect, thus it's causing the mocked queries not to be rendered. 8 | 9 | The custom addon removes the utilization of global variables to store mocked queries, instead, it fetches story parameters and render the mocked queries based on the parameters. 10 | 11 | ## Usage 12 | 13 | How to use the custom addon will remain the same as the existing addon. Please refer to the existing addon's documentation for configurations. 14 | 15 | ## References 16 | 17 | - Addon `storybook-addon-apollo-client` Github repository: https://github.com/lifeiscontent/storybook-addon-apollo-client -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/storybook-addon-apollo-client", 3 | "private": true, 4 | "version": "1.2.3", 5 | "description": "Custom Storybook Addon for Apollo Client.", 6 | "main": "preset.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "dist", 10 | "*.js", 11 | "README.md" 12 | ], 13 | "scripts": { 14 | "build": "tsc --build", 15 | "prepare": "npm run build" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/thoughtindustries/helium.git" 20 | }, 21 | "author": "Lu Jiang ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/thoughtindustries/helium/issues" 25 | }, 26 | "homepage": "https://github.com/thoughtindustries/helium#readme", 27 | "dependencies": { 28 | "@storybook/addons": "^7.0.0-beta.56", 29 | "@storybook/components": "^7.0.0-beta.56" 30 | }, 31 | "devDependencies": { 32 | "@types/react": "^18.0.28", 33 | "graphql": "^16.6.0", 34 | "react": "^18.2.0", 35 | "typescript": "^4.9.5" 36 | }, 37 | "peerDependencies": { 38 | "graphql": "*", 39 | "react": ">=16.8.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/preset.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/preset'); 2 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADDON_ID = 'addon-apolloClient'; 2 | export const PARAM_KEY = 'apolloClient'; 3 | export const NAME = 'ApolloClient'; 4 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/decorators.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PARAM_KEY } from './constants'; 3 | import { makeDecorator } from '@storybook/preview-api'; 4 | 5 | export const withApolloClient = makeDecorator({ 6 | name: 'withApolloClient', 7 | parameterName: PARAM_KEY, 8 | wrapper: (storyFn, context, { parameters }) => { 9 | const { MockedProvider, ...providerProps } = parameters; 10 | 11 | if (!MockedProvider) { 12 | console.warn( 13 | 'storybook-addon-apollo-client: MockedProvider is missing from parameters in preview.js' 14 | ); 15 | 16 | return storyFn(context); 17 | } 18 | 19 | return {storyFn(context)}; 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/index.tsx: -------------------------------------------------------------------------------- 1 | export const withApolloClient = () => (): JSX.Element => { 2 | throw new Error( 3 | 'Please look at the new configuration for storybook-addon-apollo-client: https://github.com/lifeiscontent/storybook-addon-apollo-client' 4 | ); 5 | }; 6 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/preset/addDecorator.ts: -------------------------------------------------------------------------------- 1 | import { withApolloClient } from '../decorators'; 2 | 3 | export const decorators = [withApolloClient]; 4 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/preset/index.ts: -------------------------------------------------------------------------------- 1 | export function config(entry: unknown[] = []): unknown[] { 2 | return [...entry, require.resolve('./addDecorator')]; 3 | } 4 | 5 | export function managerEntries(entry: unknown[] = []): unknown[] { 6 | return [...entry, require.resolve('../register')]; 7 | } 8 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/register.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { addons, types } from '@storybook/addons'; 3 | import { AddonPanel } from '@storybook/components'; 4 | import { ApolloClientPanel } from './panel'; 5 | import { ADDON_ID, PARAM_KEY } from './constants'; 6 | import { useTitle } from './title'; 7 | 8 | addons.register(ADDON_ID, api => { 9 | addons.add(ADDON_ID, { 10 | paramKey: PARAM_KEY, 11 | render({ active = false, key }) { 12 | return ( 13 | 14 | {!active || !api.getCurrentStoryData() ? null : } 15 | 16 | ); 17 | }, 18 | title: useTitle, 19 | type: types.PANEL 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/title.ts: -------------------------------------------------------------------------------- 1 | import { useParameter } from '@storybook/api'; 2 | import type { Parameters } from './types'; 3 | import { PARAM_KEY } from './constants'; 4 | 5 | export function useTitle(): string { 6 | const params = useParameter(PARAM_KEY); 7 | 8 | return params?.mocks?.length ? `Apollo Client (${params.mocks.length})` : 'Apollo Client (0)'; 9 | } 10 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/src/types.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | 3 | export interface MockedProviderProps { 4 | [key: string]: any; 5 | mocks?: ReadonlyArray; 6 | children?: React.ReactNode; 7 | } 8 | 9 | export type MockedProvider = React.FC; 10 | 11 | export interface Parameters extends MockedProviderProps { 12 | MockedProvider: MockedProvider; 13 | } 14 | 15 | export interface MockedResponse { 16 | request: { 17 | operationName?: string; 18 | query: DocumentNode; 19 | variables: JSON; 20 | context?: JSON; 21 | }; 22 | result?: JSON; 23 | error?: Error; 24 | } 25 | -------------------------------------------------------------------------------- /tooling/storybook-addon-apollo-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "importHelpers": true, 8 | "jsx": "react", 9 | "lib": [ 10 | "DOM", 11 | "ESNext" 12 | ], 13 | "moduleResolution": "Node", 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "outDir": "dist", 21 | "skipLibCheck": true, 22 | "sourceMap": true, 23 | "strict": true, 24 | "strictFunctionTypes": true, 25 | "strictNullChecks": true, 26 | "strictPropertyInitialization": true, 27 | "target": "ES6" 28 | }, 29 | "exclude": [ 30 | "dist", 31 | "node_modules" 32 | ], 33 | "include": [ 34 | "src" 35 | ] 36 | } -------------------------------------------------------------------------------- /tooling/tailwind-preset/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist -------------------------------------------------------------------------------- /tooling/tailwind-preset/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@thoughtindustries/helium-tailwind-preset", 3 | "version": "1.1.0", 4 | "description": "Tailwindcss Preset for use in Helium projects.", 5 | "homepage": "https://github.com/thoughtindustries/helium#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/thoughtindustries/helium.git" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/thoughtindustries/helium/issues" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "./dist/index.js", 19 | "scripts": { 20 | "build": "tsup src/index.ts --format cjs --clean", 21 | "watch": "npm run build -- --watch src", 22 | "prepublishOnly": "npm run build" 23 | }, 24 | "publishConfig": { 25 | "access": "public" 26 | }, 27 | "dependencies": { 28 | "@tailwindcss/line-clamp": "^0.4.4" 29 | }, 30 | "devDependencies": { 31 | "tsup": "^6.7.0", 32 | "typescript": "^5.0.4" 33 | }, 34 | "volta": { 35 | "node": "18.20.7", 36 | "npm": "10.8.2" 37 | }, 38 | "engines": { 39 | "node": ">=16.18.1", 40 | "npm": ">=8.19.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/.env.example: -------------------------------------------------------------------------------- 1 | # Example public variables 2 | HELIUM_PUBLIC_APP_VERSION='v1' 3 | 4 | # Example private variables 5 | HELIUM_PRIVATE_SECRET='secret value' -------------------------------------------------------------------------------- /tooling/template-base-essentials/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist 10 | *.local 11 | ti-config.json -------------------------------------------------------------------------------- /tooling/template-base-essentials/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | engine-strict=true -------------------------------------------------------------------------------- /tooling/template-base-essentials/.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "installDependencies": true, 3 | "startCommand": "echo 'Welcome to Helium starter app'" 4 | } -------------------------------------------------------------------------------- /tooling/template-base-essentials/__npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | engine-strict=true -------------------------------------------------------------------------------- /tooling/template-base-essentials/_gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist 10 | *.local 11 | renderer/tailwind.css 12 | ti-config.json -------------------------------------------------------------------------------- /tooling/template-base-essentials/components/HomepageHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const HomepageHeader = (props: { image?: string; title?: string; body?: string }) => { 4 | return ( 5 |
6 | {props.image && } 7 | {props.title &&
{props.title}
} 8 | {props.body &&
{props.body}
} 9 |
10 | ); 11 | }; 12 | 13 | export default HomepageHeader; 14 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/components/RenderLinks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const RenderLinks = (props: { 4 | links: Array<{ href: string; label: string }>; 5 | linkStyling?: string; 6 | title?: string; 7 | }) => { 8 | const LINKS_ARRAY = []; 9 | if (props.links !== null) { 10 | for (let i = 0; i < props?.links.length; i++) { 11 | LINKS_ARRAY.push( 12 |
  • 13 | {props.links[i].label} 14 |
  • 15 | ); 16 | } 17 | } else { 18 | throw new Error('props.links must be an Object'); 19 | } 20 | return ( 21 |
    22 | {props.title &&
    {props.title}
    } 23 | {LINKS_ARRAY} 24 |
    25 | ); 26 | }; 27 | 28 | export default RenderLinks; 29 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/components/TitleAndBody.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TitleAndBody = (props: { title?: string; body?: string }) => { 4 | return ( 5 |
    6 |
    7 | {props.title &&
    {props.title}
    } 8 | {props.body &&
    {props.body}
    } 9 |
    10 |
    11 | ); 12 | }; 13 | 14 | export default TitleAndBody; 15 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | // public env variables... 5 | readonly HELIUM_PUBLIC_APP_VERSION: string; 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } 11 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/locales/.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore everything 2 | * 3 | #dont ignore this file 4 | !.gitignore -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/Link.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { usePageContext } from './usePageContext'; 3 | 4 | export { Link }; 5 | 6 | function Link(props) { 7 | const pageContext = usePageContext(); 8 | const className = [props.className, pageContext.urlPathname === props.href && 'is-active'] 9 | .filter(Boolean) 10 | .join(' '); 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/PageWrapper.css: -------------------------------------------------------------------------------- 1 | /* This CSS is common to all pages */ 2 | 3 | /* import inter font */ 4 | @import url('https://rsms.me/inter/inter.css'); 5 | @import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap'); 6 | 7 | body { 8 | margin: 0; 9 | font-family: sans-serif; 10 | } 11 | * { 12 | box-sizing: border-box; 13 | } 14 | a { 15 | text-decoration: none; 16 | } 17 | 18 | .navitem { 19 | padding: 3px 10px; 20 | } 21 | .navitem.is-active { 22 | background-color: #eee; 23 | } 24 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/PageWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './tailwind.css'; 3 | import './PageWrapper.css'; 4 | import { PageContextProvider } from './usePageContext'; 5 | import { PageWrapperProps } from './../types'; 6 | 7 | export { PageWrapper }; 8 | 9 | function PageWrapper({ pageContext, children }: PageWrapperProps): JSX.Element { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/_error.page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export { Page }; 4 | 5 | function Page({ is404 }: { is404: boolean }) { 6 | if (is404) { 7 | return ( 8 | <> 9 |

    404 Page Not Found

    10 |

    This page could not be found.

    11 | 12 | ); 13 | } else { 14 | return ( 15 | <> 16 |

    500 Internal Server Error

    17 |

    Something went wrong.

    18 | 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/getPageMeta.ts: -------------------------------------------------------------------------------- 1 | import { PageContext } from './../types'; 2 | 3 | export { getPageMeta }; 4 | 5 | function getPageMeta(pageContext: PageContext) { 6 | const title = fetchProperty(pageContext, 'title'); 7 | const description = fetchProperty(pageContext, 'description'); 8 | return { title, description }; 9 | } 10 | 11 | function hasKey(obj: O, key: PropertyKey): key is keyof O { 12 | return key in obj; 13 | } 14 | 15 | function fetchProperty(pageContext: PageContext, property: string) { 16 | /** 17 | * pageContext.exports.documentProps for static titles (defined in the `export { documentProps }` of the page's `.page.js`) 18 | * pageContext.documentProps for dynamic tiles (defined in the `export addContextProps()` of the page's `.page.server.js`) 19 | */ 20 | let result; 21 | if (hasKey(pageContext.exports.documentProps || {}, property)) { 22 | result = (pageContext.exports.documentProps || {})[property]; 23 | } else if (hasKey(pageContext.documentProps || {}, property)) { 24 | result = (pageContext.documentProps || {})[property]; 25 | } 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import translationResources from '../locales/translations.json'; 4 | 5 | i18n.use(initReactI18next).init({ 6 | lng: 'en', 7 | fallbackLng: 'en', 8 | ns: ['lms'], 9 | defaultNS: 'lms', 10 | interpolation: { 11 | prefix: '%{', 12 | suffix: '}' 13 | }, 14 | resources: translationResources, 15 | react: { useSuspense: false } 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/renderer/usePageContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { PageContext } from '../types'; 3 | 4 | export { PageContextProvider }; 5 | export { usePageContext }; 6 | 7 | const Context = React.createContext(undefined); 8 | 9 | function PageContextProvider({ 10 | pageContext, 11 | children 12 | }: { 13 | pageContext: PageContext; 14 | children: React.ReactNode; 15 | }) { 16 | return {children}; 17 | } 18 | 19 | function usePageContext() { 20 | const pageContext = useContext(Context); 21 | 22 | if (!pageContext) { 23 | throw new Error('Expected a Page Context, but no Page Context was found'); 24 | } 25 | 26 | return pageContext; 27 | } 28 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/server/index.ts: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === 'production'; 2 | import { setupHeliumServer } from '@thoughtindustries/helium-server'; 3 | const root = `${__dirname}/..`; 4 | 5 | startServer(); 6 | 7 | async function startServer() { 8 | let viteDevServer; 9 | 10 | if (!isProduction) { 11 | const vite = require('vite'); 12 | viteDevServer = await vite.createServer({ 13 | root, 14 | server: { middlewareMode: true } 15 | }); 16 | } 17 | 18 | const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; 19 | const app = await setupHeliumServer(root, viteDevServer, port); 20 | 21 | app.listen(port); 22 | console.log(`Server running at http://localhost:${port}`); 23 | } 24 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require('@thoughtindustries/helium-tailwind-preset')], 3 | content: [ 4 | './atoms/**/*.{js,jsx,ts,tsx}', 5 | './pages/**/*.{js,jsx,ts,tsx}', 6 | './components/**/*.{js,jsx,ts,tsx}', 7 | './dist/**/*.{js,jsx,ts,tsx}' 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | 'link-rgba': 'rgba(0, 159, 180, 1)' 13 | } 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /tooling/template-base-essentials/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { tiConfig } from '@thoughtindustries/helium-server'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig(async () => { 5 | const remarkFrontmatter = await import('remark-frontmatter'); 6 | const remarkMdxFrontmatter = await import('remark-mdx-frontmatter'); 7 | const mdx = await import('@mdx-js/rollup'); 8 | const mdxOptions = { 9 | remarkPlugins: [remarkFrontmatter.default, remarkMdxFrontmatter.default], 10 | rehypePlugins: [] 11 | }; 12 | 13 | tiConfig.plugins.push(mdx.default(mdxOptions)); 14 | 15 | return tiConfig; 16 | }); 17 | -------------------------------------------------------------------------------- /tooling/template-base/.env.example: -------------------------------------------------------------------------------- 1 | # Example public variables 2 | HELIUM_PUBLIC_APP_VERSION='v1' 3 | 4 | # Example private variables 5 | HELIUM_PRIVATE_SECRET='secret value' -------------------------------------------------------------------------------- /tooling/template-base/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist 10 | *.local 11 | ti-config.json -------------------------------------------------------------------------------- /tooling/template-base/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | engine-strict=true -------------------------------------------------------------------------------- /tooling/template-base/.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "installDependencies": true, 3 | "startCommand": "echo 'Welcome to Helium starter app'" 4 | } -------------------------------------------------------------------------------- /tooling/template-base/__npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | engine-strict=true -------------------------------------------------------------------------------- /tooling/template-base/_gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # env 5 | .env 6 | 7 | # misc 8 | .DS_Store 9 | dist 10 | *.local 11 | renderer/tailwind.css 12 | ti-config.json -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/DropDownClosed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DropDownClosed = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default DropDownClosed; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/DropDownOpen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DropDownOpen = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default DropDownOpen; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/Hamburger.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Hamburger = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default Hamburger; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/ListViewSelector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ListViewSelector = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default ListViewSelector; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/Share.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Share = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default Share; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Assets/Xicon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Xicon = ( 4 | 5 | 12 | 13 | ); 14 | 15 | export default Xicon; 16 | -------------------------------------------------------------------------------- /tooling/template-base/components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Banner = (props: { heading: string; subtext: string }) => { 4 | return ( 5 |
    6 |
    7 | {props.heading} 8 |
    9 |
    {props.subtext}
    10 |
    11 | ); 12 | }; 13 | 14 | export default Banner; 15 | -------------------------------------------------------------------------------- /tooling/template-base/components/CTA/ButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Share from '../Assets/Share'; 3 | 4 | const ButtonLink = (props: { text: string; linkUrl: string }) => { 5 | return ( 6 |
    14 | ); 15 | }; 16 | 17 | export default ButtonLink; 18 | -------------------------------------------------------------------------------- /tooling/template-base/components/CTA/CTA.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CTA = (props: { headline: string; body: string; buttonUrl: string; buttonText: string }) => { 4 | return ( 5 |
    6 |
    {props.headline}
    7 |
    8 |
    {props.body}
    9 |
    10 | 11 | 14 | 15 |
    16 | ); 17 | }; 18 | 19 | export default CTA; 20 | -------------------------------------------------------------------------------- /tooling/template-base/components/CTA/CallToActionWithLinks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ButtonLink from './ButtonLink'; 3 | 4 | const CallToActionWithLinks = (props: { 5 | headline: string; 6 | description: string; 7 | links: { 8 | text: string; 9 | linkUrl: string; 10 | }[]; 11 | }) => { 12 | const linkEl = props.links.map((link, i) => ( 13 |
    14 | 15 |
    16 | )); 17 | 18 | return ( 19 |
    20 |
    {props.headline}
    21 |
    {props.description}
    22 |
    {linkEl}
    23 |
    24 | ); 25 | }; 26 | 27 | export default CallToActionWithLinks; 28 | -------------------------------------------------------------------------------- /tooling/template-base/components/CatalogAndAggreg/CatalogError.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import { useCatalog } from '@thoughtindustries/catalog'; 3 | 4 | const CatalogError = ({ children }: { children: ReactElement }): JSX.Element => { 5 | const { params } = useCatalog(); 6 | const { error } = params; 7 | 8 | if (error) { 9 | return <>{error}; 10 | } 11 | 12 | return children; 13 | }; 14 | 15 | CatalogError.displayName = 'CatalogError'; 16 | export default CatalogError; 17 | -------------------------------------------------------------------------------- /tooling/template-base/components/CatalogAndAggreg/catalog-item-asset-block.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { usePageContext } from '../../renderer/usePageContext'; 4 | 5 | interface ItemAssetBlockProps { 6 | asset?: string; 7 | } 8 | 9 | const ItemAssetBlock = ({ asset }: ItemAssetBlockProps) => { 10 | const { appearance } = usePageContext(); 11 | const itemAsset = asset ? asset : appearance?.logoAsset; 12 | return ( 13 | 17 | ); 18 | }; 19 | 20 | ItemAssetBlock.displayName = 'ItemAssetBlock'; 21 | 22 | export default ItemAssetBlock; 23 | -------------------------------------------------------------------------------- /tooling/template-base/components/CourseSidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | 4 | export default function CourseSidebar({ 5 | coursePages, 6 | currentPageIndex, 7 | setPage 8 | }: { 9 | coursePages: JSX.Element[]; 10 | currentPageIndex: number; 11 | setPage: (page: number) => void; 12 | }) { 13 | const pageLabels = coursePages.map(page => page.type.label); 14 | 15 | return ( 16 |
    17 |
      18 | {pageLabels.map((label, i) => { 19 | const active = i === currentPageIndex; 20 | return ( 21 |
    • 22 | 33 |
    • 34 | ); 35 | })} 36 |
    37 |
    38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /tooling/template-base/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Logo from '../Logo/Logo'; 3 | 4 | const Footer = () => { 5 | return ( 6 | <> 7 |
    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | Home 15 | Catalog 16 | Help 17 |
    18 |
    19 |
    20 | © 2022, Astro Theme Powered by Thought Industries 21 |
    22 |
    23 | 24 | ); 25 | }; 26 | 27 | export default Footer; 28 | -------------------------------------------------------------------------------- /tooling/template-base/components/Hero/Hero.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeroImage from '../Assets/HeroImage'; 3 | 4 | const Hero = (props: { headline: string; body: string; buttonUrl: string; buttonText: string }) => { 5 | return ( 6 | <> 7 |
    8 |
    9 | 10 |
    11 |
    {props.headline}
    12 |
    {props.body}
    13 | 20 |
    21 |
    22 | 23 | ); 24 | }; 25 | 26 | export default Hero; 27 | -------------------------------------------------------------------------------- /tooling/template-base/components/LearnerAccess/Context/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { LearnerAccessContextType } from '../Types/types'; 3 | 4 | const LearnerAccessContext = createContext(undefined); 5 | 6 | export default LearnerAccessContext; 7 | -------------------------------------------------------------------------------- /tooling/template-base/components/LearnerAccess/Context/use-context.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import LearnerAccessContext from './context'; 3 | 4 | export default function useLearnerAccess() { 5 | const context = useContext(LearnerAccessContext); 6 | if (!context) { 7 | throw new Error('No context found for LearnerAccess'); 8 | } 9 | return context; 10 | } 11 | -------------------------------------------------------------------------------- /tooling/template-base/components/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import defaultLogo from '../Assets/logoImage'; 3 | import { usePageContext } from '../../renderer/usePageContext'; 4 | 5 | const Logo = (props: { size: string }) => { 6 | const { appearance } = usePageContext(); 7 | 8 | const companyLogo = appearance?.logoAsset ? appearance?.logoAsset : defaultLogo; 9 | 10 | let logoElement; 11 | if (props.size === 'large') { 12 | logoElement = ( 13 | 14 | 15 | 16 | ); 17 | } else if (props.size === 'small') { 18 | logoElement = ( 19 | 20 | 21 | 22 | ); 23 | } else { 24 | logoElement = ( 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | return <>{logoElement}; 32 | }; 33 | 34 | export default Logo; 35 | -------------------------------------------------------------------------------- /tooling/template-base/components/Navigation/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { usePageContext } from '../../renderer/usePageContext'; 3 | import CurrentUserSmallScreenNavBar from './CurrentUserSmallScreenNavBar'; 4 | import CurrentUserNavBar from './CurrentUserNavBar'; 5 | import UserLoginNavBar from './UserLoginNavBar'; 6 | 7 | const NavBar = () => { 8 | const pageContext = usePageContext(); 9 | const { currentUser } = pageContext; 10 | 11 | let navbar; 12 | if (currentUser) { 13 | // signed in 14 | navbar = ( 15 | <> 16 |
    17 | 18 |
    19 |
    20 | 21 |
    22 | 23 | ); 24 | } else { 25 | // signed out 26 | navbar = ; 27 | } 28 | return navbar; 29 | }; 30 | 31 | export default NavBar; 32 | -------------------------------------------------------------------------------- /tooling/template-base/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | // public env variables... 5 | readonly HELIUM_PUBLIC_APP_VERSION: string; 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } 11 | -------------------------------------------------------------------------------- /tooling/template-base/locales/.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore everything 2 | * 3 | #dont ignore this file 4 | !.gitignore -------------------------------------------------------------------------------- /tooling/template-base/pages/catalog/index.page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Footer from '../../components/Footer/Footer'; 3 | import Banner from '../../components/Banner'; 4 | import NavBar from '../../components/Navigation/NavBar'; 5 | import CatalogAndAggregation from '../../components/CatalogAndAggreg/CatalogAndAggregation'; 6 | import { HydratedContentItem } from '@thoughtindustries/content'; 7 | 8 | export { Page }; 9 | export { documentProps }; 10 | 11 | const documentProps = { 12 | title: 'Catalog Page', 13 | description: 'The catalog page' 14 | }; 15 | 16 | function Page() { 17 | return ( 18 | <> 19 |
    20 | 21 | 25 | { 27 | throw new Error('Function not implemented.'); 28 | }} 29 | /> 30 |
    31 |
    32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tooling/template-base/pages/learn/sign_in.page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SigninPage from '../../components/Signin/SigninPage'; 3 | 4 | export { Page }; 5 | 6 | function Page() { 7 | return ( 8 |
    9 | 10 |
    11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/Link.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { usePageContext } from './usePageContext'; 3 | 4 | export { Link }; 5 | 6 | function Link(props) { 7 | const pageContext = usePageContext(); 8 | const className = [props.className, pageContext.urlPathname === props.href && 'is-active'] 9 | .filter(Boolean) 10 | .join(' '); 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/PageWrapper.css: -------------------------------------------------------------------------------- 1 | /* This CSS is common to all pages */ 2 | 3 | /* import inter font */ 4 | @import url('https://rsms.me/inter/inter.css'); 5 | @import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed&display=swap'); 6 | 7 | body { 8 | margin: 0; 9 | font-family: sans-serif; 10 | } 11 | * { 12 | box-sizing: border-box; 13 | } 14 | a { 15 | text-decoration: none; 16 | } 17 | 18 | .navitem { 19 | padding: 3px 10px; 20 | } 21 | .navitem.is-active { 22 | background-color: #eee; 23 | } 24 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/PageWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './tailwind.css'; 3 | import './PageWrapper.css'; 4 | import { PageContextProvider } from './usePageContext'; 5 | import { PageWrapperProps } from './../types'; 6 | 7 | export { PageWrapper }; 8 | 9 | function PageWrapper({ pageContext, children }: PageWrapperProps): JSX.Element { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/_error.page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export { Page }; 4 | 5 | function Page({ is404 }: { is404: boolean }) { 6 | if (is404) { 7 | return ( 8 | <> 9 |

    404 Page Not Found

    10 |

    This page could not be found.

    11 | 12 | ); 13 | } else { 14 | return ( 15 | <> 16 |

    500 Internal Server Error

    17 |

    Something went wrong.

    18 | 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/dropDownClosed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/dropDownOpen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/getPageMeta.ts: -------------------------------------------------------------------------------- 1 | import { PageContext } from './../types'; 2 | 3 | export { getPageMeta }; 4 | 5 | function getPageMeta(pageContext: PageContext) { 6 | const title = fetchProperty(pageContext, 'title'); 7 | const description = fetchProperty(pageContext, 'description'); 8 | return { title, description }; 9 | } 10 | 11 | function hasKey(obj: O, key: PropertyKey): key is keyof O { 12 | return key in obj; 13 | } 14 | 15 | function fetchProperty(pageContext: PageContext, property: string) { 16 | /** 17 | * pageContext.exports.documentProps for static titles (defined in the `export { documentProps }` of the page's `.page.js`) 18 | * pageContext.documentProps for dynamic tiles (defined in the `export addContextProps()` of the page's `.page.server.js`) 19 | */ 20 | let result; 21 | if (hasKey(pageContext.exports.documentProps || {}, property)) { 22 | result = (pageContext.exports.documentProps || {})[property]; 23 | } else if (hasKey(pageContext.documentProps || {}, property)) { 24 | result = (pageContext.documentProps || {})[property]; 25 | } 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/hamburger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import translationResources from '../locales/translations.json'; 4 | 5 | i18n.use(initReactI18next).init({ 6 | lng: 'en', 7 | fallbackLng: 'en', 8 | ns: ['lms'], 9 | defaultNS: 'lms', 10 | interpolation: { 11 | prefix: '%{', 12 | suffix: '}' 13 | }, 14 | resources: translationResources, 15 | react: { useSuspense: false } 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/listViewSelector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtindustries/helium/8d96e736d46edda55b4109c8c96ce6a596a56619/tooling/template-base/renderer/trees.png -------------------------------------------------------------------------------- /tooling/template-base/renderer/usePageContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { PageContext } from '../types'; 3 | 4 | export { PageContextProvider }; 5 | export { usePageContext }; 6 | 7 | const Context = React.createContext(undefined); 8 | 9 | function PageContextProvider({ 10 | pageContext, 11 | children 12 | }: { 13 | pageContext: PageContext; 14 | children: React.ReactNode; 15 | }) { 16 | return {children}; 17 | } 18 | 19 | function usePageContext() { 20 | const pageContext = useContext(Context); 21 | 22 | if (!pageContext) { 23 | throw new Error('Expected a Page Context, but no Page Context was found'); 24 | } 25 | 26 | return pageContext; 27 | } 28 | -------------------------------------------------------------------------------- /tooling/template-base/renderer/xicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tooling/template-base/server/index.ts: -------------------------------------------------------------------------------- 1 | const isProduction = process.env.NODE_ENV === 'production'; 2 | import { setupHeliumServer } from '@thoughtindustries/helium-server'; 3 | const root = `${__dirname}/..`; 4 | 5 | startServer(); 6 | 7 | async function startServer() { 8 | let viteDevServer; 9 | 10 | if (!isProduction) { 11 | const vite = require('vite'); 12 | viteDevServer = await vite.createServer({ 13 | root, 14 | server: { middlewareMode: true } 15 | }); 16 | } 17 | 18 | const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; 19 | const app = await setupHeliumServer(root, viteDevServer, port); 20 | 21 | app.listen(port); 22 | console.log(`Server running at http://localhost:${port}`); 23 | } 24 | -------------------------------------------------------------------------------- /tooling/template-base/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require('@thoughtindustries/helium-tailwind-preset')], 3 | content: [ 4 | './atoms/**/*.{js,jsx,ts,tsx}', 5 | './pages/**/*.{js,jsx,ts,tsx}', 6 | './components/**/*.{js,jsx,ts,tsx}', 7 | './dist/**/*.{js,jsx,ts,tsx}' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /tooling/template-base/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { tiConfig } from '@thoughtindustries/helium-server'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig(async () => { 5 | const remarkFrontmatter = await import('remark-frontmatter'); 6 | const remarkMdxFrontmatter = await import('remark-mdx-frontmatter'); 7 | const mdx = await import('@mdx-js/rollup'); 8 | const mdxOptions = { 9 | remarkPlugins: [remarkFrontmatter.default, remarkMdxFrontmatter.default], 10 | rehypePlugins: [] 11 | }; 12 | 13 | tiConfig.plugins.push(mdx.default(mdxOptions)); 14 | 15 | return tiConfig; 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.stories.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "**/__tests__/**"], 4 | "include": ["./packages/**/*.stories.tsx"] 5 | } 6 | --------------------------------------------------------------------------------