diff --git a/packages/unity-bootstrap-theme/.storybook/decorators.jsx b/packages/unity-bootstrap-theme/.storybook/decorators.jsx index f5e632a20a..5ec5eab362 100644 --- a/packages/unity-bootstrap-theme/.storybook/decorators.jsx +++ b/packages/unity-bootstrap-theme/.storybook/decorators.jsx @@ -1,15 +1,32 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; + +export const windowLoadEvent = (storyFn) => { + const mount = () => { + // console.log("sb mounting"); + + // custom events created by eventSpy.js to allow storybook to dispatch load events after the page is loaded + document.dispatchEvent(new Event("sb_DOMContentLoaded")); + window.dispatchEvent(new Event('sb_load')); + } + + const unmount = () => { + // console.log("sb unmounting"); + + // bootstrap script has functionality initiated with window load event, + // so we need to reload the page every time we unmount the story. + // We set the opacity to 0 to avoid flickering. + document.body.style.opacity = "0"; + + // variable to prevent calling mount function before the page is reloaded + window.unloading = true; + window.location.reload(); + } -export const forceReloadOfStory = (storyFn, context) => { useEffect(() => { - setTimeout(() => { - context.globals.shouldReload = true; - }, 100); - return () => { - if (context.globals.shouldReload) { - window.location.reload(); - } - }; + if (!window.unloading) { + mount() + } + return unmount }, []); return storyFn(); diff --git a/packages/unity-bootstrap-theme/.storybook/eventSpy.js b/packages/unity-bootstrap-theme/.storybook/eventSpy.js new file mode 100644 index 0000000000..1c85cfa49f --- /dev/null +++ b/packages/unity-bootstrap-theme/.storybook/eventSpy.js @@ -0,0 +1,13 @@ +var originalAddEventListener = EventTarget.prototype.addEventListener; +EventTarget.prototype.addEventListener = function (type, listener, options) { + if(listener.uidEvent && (type === "load" || type === "DOMContentLoaded")) { + /** + * Used by storybook windowLoadEvent decorator + * creates custom events sb_load and sb_DOMContentLoaded + * to allow storybook to dispatch load events after the page is loaded + * */ + originalAddEventListener.call(this, `sb_${type}`, listener, options); + } + + originalAddEventListener.call(this, type, listener, options); +}; diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/addon.js b/packages/unity-bootstrap-theme/.storybook/local-addon/addon.js index 20e3985e4e..c2539aee21 100644 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/addon.js +++ b/packages/unity-bootstrap-theme/.storybook/local-addon/addon.js @@ -1,8 +1,6 @@ import React from 'react'; import { AddonPanel } from '@storybook/components'; -import { Source } from '@storybook/blocks'; import { addons, types } from '@storybook/addons'; -import { formatWithBabelParser } from './helpers'; import { Toggle } from '../../../../.storybook-config/Toggle' addons.register('local-addon', (api) => { @@ -12,13 +10,12 @@ addons.register('local-addon', (api) => { type: types.PANEL, paramKey: 'initFunc', render: ({active, key}) => { - const data = api.getCurrentStoryData(); - const initFunc = data?.parameters?.initFunc?.code || ''; - const code = formatWithBabelParser(`${initFunc}`); - return( - +
+ This component requires Javascript.

+ View the documentation on including unity in your project +
)}, }); diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withFooter.js b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withFooter.js index f3a5d1f4f2..0f33d32028 100644 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withFooter.js +++ b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withFooter.js @@ -1,27 +1,15 @@ -import React, { useEffect, useRef} from 'react'; +import React from 'react'; import { makeDecorator } from '@storybook/addons'; - import { GlobalElementsOnly as Footer } from "../../../stories/organisms/global-footer/global-footer.templates"; -import { initFooterGA } from "../../../stories/organisms/global-footer/global-footer" export const withFooter = makeDecorator({ name: 'withFooter', parameterName: 'footer', skipIfNoParametersOrOptions: true, wrapper: (storyFn, context) => { - const loaded = useRef(0); - useEffect(()=>{ - if(document.readyState !== "loading") { - initFooterGA() - } - },[loaded.current]); - loaded.current++; - const { globals, parameters } = context; - const footer = parameters?.footer; const isFooterActive = (globals.footer == true && footer?.disable !== true) || footer.forced == true; - const isStory = context.viewMode === 'story'; return <> diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withHeader.js b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withHeader.js index 395afd71e9..bc1c1b3d3b 100644 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withHeader.js +++ b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withHeader.js @@ -1,28 +1,16 @@ -import React, { useEffect, useRef} from 'react'; +import React from 'react'; import { makeDecorator } from '@storybook/addons'; -import { Basic as Header } from "../../../stories/organisms/global-header/global-header.templates"; - -import { initGlobalHeader } from "../../../src/js/storybook-global-header"; +import { Basic as Header } from "../../../stories/organisms/global-header/global-header.templates.jsx"; export const withHeader = makeDecorator({ name: 'withHeader', parameterName: 'header', skipIfNoParametersOrOptions: true, wrapper: (storyFn, context) => { - const loaded = useRef(0); - useEffect(()=>{ - if(document.readyState !== "loading") { - initGlobalHeader() - } - },[loaded.current]); - loaded.current++; - const { globals, parameters } = context; - const header = parameters?.header; const isHeaderActive = (globals.header == true && header?.disable !== true) || header.forced === true; - const isStory = context.viewMode === 'story'; return <> diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withInitFunc.js b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withInitFunc.js deleted file mode 100644 index db95f2d47b..0000000000 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withInitFunc.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import { makeDecorator } from '@storybook/addons'; - -export const withInitFunc = makeDecorator({ - name: 'withInitFunc', - parameterName: 'initFunc', - skipIfNoParametersOrOptions: true, - wrapper: (storyFn, context) => { - React.useEffect(()=>{ - const initFunc = context?.parameters?.initFunc?.code; - if (typeof initFunc === "function") { - initFunc() - } - },[]) - return storyFn(context); - } -}); diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withLoadEvent.js b/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withLoadEvent.js deleted file mode 100644 index 6de93ee762..0000000000 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/decorators/withLoadEvent.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect, useRef} from 'react'; - -import { makeDecorator } from '@storybook/addons'; - -export const withLoadEvent = makeDecorator({ - name: 'withLoadEvent', - parameterName: 'loadEvent', - skipIfNoParametersOrOptions: true, - wrapper: (storyFn, context) => { - const loaded = useRef(0); - useEffect(()=>{ - if(document.readyState !== "loading") { - // globalThis = window - globalThis.dispatchEvent(new Event("DOMContentLoaded")); - globalThis.dispatchEvent(new Event('load')); - } - if(typeof globalThis.initDataLayer === "function") { - globalThis.initDataLayer(); - } - },[loaded.current]); - loaded.current++; - return storyFn(context) - } -}); diff --git a/packages/unity-bootstrap-theme/.storybook/local-addon/entry.js b/packages/unity-bootstrap-theme/.storybook/local-addon/entry.js index 76d4b6985a..c59229045d 100644 --- a/packages/unity-bootstrap-theme/.storybook/local-addon/entry.js +++ b/packages/unity-bootstrap-theme/.storybook/local-addon/entry.js @@ -5,10 +5,6 @@ import { import { withFooter } from './decorators/withFooter'; import { withHeader } from './decorators/withHeader'; -import { withInitFunc } from './decorators/withInitFunc'; -import { withLoadEvent } from './decorators/withLoadEvent'; - -import "../../src/js/data-layer.js"; export const parameters = { header: { @@ -21,8 +17,7 @@ export const parameters = { disable: false }, initFunc: { - disable: true, - code: null + disable: true }, docs:{ source: { @@ -40,8 +35,6 @@ export const globals = { footer: false } export const decorators = [ - withLoadEvent, - withInitFunc, withHeader, withFooter, ] diff --git a/packages/unity-bootstrap-theme/.storybook/main.js b/packages/unity-bootstrap-theme/.storybook/main.js index 80e3ce3d3f..a696a46574 100644 --- a/packages/unity-bootstrap-theme/.storybook/main.js +++ b/packages/unity-bootstrap-theme/.storybook/main.js @@ -4,6 +4,7 @@ export default { staticDirs: ['../dist'], stories: ["../stories/**/*.stories.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"], addons: [ + "./local-addon", "../../../.storybook-config", "../../../.storybook-config/dataLayerListener", "@whitespace/storybook-addon-html", diff --git a/packages/unity-bootstrap-theme/.storybook/preview.js b/packages/unity-bootstrap-theme/.storybook/preview.js index 121823f1b0..fc622e9c19 100644 --- a/packages/unity-bootstrap-theme/.storybook/preview.js +++ b/packages/unity-bootstrap-theme/.storybook/preview.js @@ -1,8 +1,13 @@ +import "./eventSpy.js"; import "../src/scss/unity-bootstrap-theme.bundle.scss"; +import { default as bootstrap } from "bootstrap/js/index.umd.js"; +globalThis.bootstrap = bootstrap; +import { default as udsBootstrap } from "../src/js/unity-bootstrap.js"; +globalThis.udsBootstrap = udsBootstrap; + import { removeFontAwesomeChanges } from "./local-addon/helpers"; -// Import all the Bootstrap bundle -import "../../../node_modules/bootstrap/dist/js/bootstrap.bundle.min"; +import { windowLoadEvent } from "./decorators.jsx"; const parameters = { options: { @@ -58,7 +63,8 @@ const parameters = { /** @type { import('@storybook/react').Preview } */ const preview = { - parameters + parameters, + decorators: [windowLoadEvent], }; export default preview; diff --git a/packages/unity-bootstrap-theme/jsconfig.json b/packages/unity-bootstrap-theme/jsconfig.json new file mode 100644 index 0000000000..1d95ba1e0b --- /dev/null +++ b/packages/unity-bootstrap-theme/jsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM"], + "resolveJsonModule": true, + "jsx": "react", + "allowImportingTsExtensions": true, + "esModuleInterop": true, + "moduleResolution": "node16", + "module": "node16", + "allowSyntheticDefaultImports": true, + "paths": { + "@shared/*": [ "../../shared/*" ], + }, + }, + "include": [ + "src/**/*", + "stories/**/*", + "../../shared/**/*", + ], +} diff --git a/packages/unity-bootstrap-theme/package.json b/packages/unity-bootstrap-theme/package.json index 3e07bbf969..9551ea54e6 100644 --- a/packages/unity-bootstrap-theme/package.json +++ b/packages/unity-bootstrap-theme/package.json @@ -13,7 +13,9 @@ "url": "git+https://github.com/ASU/asu-unity-stack.git" }, "scripts": { - "build": "vite build", + "build": "yarn build:css && yarn build:js", + "build:css": "vite build", + "build:js": "vite build --config vite.config.bundle.js", "watch": "rimraf ./dist && webpack --watch && rimraf ./.tmp", "storybook": "storybook dev -p 9000", "build-storybook": "storybook build -o ../../build/$npm_package_name", @@ -77,14 +79,6 @@ "webpack-node-externals": "^3.0.0" }, "exports": { - "./js/global-header.js": { - "module": "./src/js/storybook-global-header.js", - "default": "./src/js/global-header.js" - }, - "./js/data-layer.js": { - "module": "./src/js/storybook-data-layer.js", - "default": "./src/js/data-layer.js" - }, "./*": "./*", ".": "./dist/css/unity-bootstrap-theme.css" }, diff --git a/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.js b/packages/unity-bootstrap-theme/src/js/anchor-menu.js similarity index 94% rename from packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.js rename to packages/unity-bootstrap-theme/src/js/anchor-menu.js index 3200e706d3..d13fb7ea26 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.js +++ b/packages/unity-bootstrap-theme/src/js/anchor-menu.js @@ -1,9 +1,15 @@ -const bootstrap = require('bootstrap'); +import { EventHandler } from "./bootstrap-helper"; -function initializeAnchorMenu () { +function initAnchorMenu () { const HEADER_IDS = ['asu-header', 'asuHeader']; const globalHeaderId = HEADER_IDS.find((id) => document.getElementById(id)); + + if (globalHeaderId === undefined) { + // Asu header not found in the DOM. + return; + } + const globalHeader = document.getElementById(globalHeaderId); const navbar = document.getElementById('uds-anchor-menu'); const navbarOriginalParent = navbar.parentNode; @@ -115,4 +121,6 @@ function initializeAnchorMenu () { } }; -export { initializeAnchorMenu }; +EventHandler.on(window, 'load.uds.anchor-menu', initAnchorMenu); + +export { initAnchorMenu }; diff --git a/packages/unity-bootstrap-theme/src/js/banner.js b/packages/unity-bootstrap-theme/src/js/banner.js new file mode 100644 index 0000000000..42192d9771 --- /dev/null +++ b/packages/unity-bootstrap-theme/src/js/banner.js @@ -0,0 +1,84 @@ +// should we just use bootstrap's Alert + +import { + BaseComponent, + EventHandler, + enableDismissTrigger, + defineJQueryPlugin, +} from "./bootstrap-helper.js"; + +/** + * Constants + */ + +const NAME = 'banner-container' +const DATA_KEY = 'uds.banner' +const EVENT_KEY = `.${DATA_KEY}` + +const EVENT_CLOSE = `close${EVENT_KEY}` +const EVENT_CLOSED = `closed${EVENT_KEY}` +const CLASS_NAME_FADE = 'fade' +const CLASS_NAME_SHOW = 'show' + +/** + * Class definition + */ + +class Banner extends BaseComponent { + // Getters + static get NAME() { + return NAME + } + + // Public + close() { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE) + + if (closeEvent.defaultPrevented) { + return + } + + this._element.classList.remove(CLASS_NAME_SHOW) + + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE) + this._queueCallback(() => this._destroyElement(), this._element, isAnimated) + } + + // Private + _destroyElement() { + this._element.remove() + EventHandler.trigger(this._element, EVENT_CLOSED) + this.dispose() + } + + // Static + static jQueryInterface(config) { + return this.each(function () { + const data = Banner.getOrCreateInstance(this) + + if (typeof config !== 'string') { + return + } + + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) + } + + data[config](this) + }) + } +} + +/** + * Data API implementation + */ + +enableDismissTrigger(Banner, 'close') + +/** + * jQuery + */ + +defineJQueryPlugin(Banner) + +export default Banner diff --git a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote-animated.js b/packages/unity-bootstrap-theme/src/js/blockquote-animated.js similarity index 77% rename from packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote-animated.js rename to packages/unity-bootstrap-theme/src/js/blockquote-animated.js index 3acc52fa28..d41fb235ab 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote-animated.js +++ b/packages/unity-bootstrap-theme/src/js/blockquote-animated.js @@ -1,5 +1,6 @@ -function initializeBlockquoteAnimation () { - window.addEventListener("load", () => { +import { EventHandler } from "./bootstrap-helper"; + +function initBlockquoteAnimation () { // Find all marks with a class starting with pen- const markers = document.querySelectorAll('mark[class^="pen-"]'); @@ -24,7 +25,8 @@ function initializeBlockquoteAnimation () { markers.forEach((mark) => { observer.observe(mark); }); - }); } -export { initializeBlockquoteAnimation }; +EventHandler.on(window, 'load.uds.blockquote-animation', initBlockquoteAnimation); + +export { initBlockquoteAnimation }; diff --git a/packages/unity-bootstrap-theme/src/js/bootstrap-helper.js b/packages/unity-bootstrap-theme/src/js/bootstrap-helper.js new file mode 100644 index 0000000000..af8ada0cf0 --- /dev/null +++ b/packages/unity-bootstrap-theme/src/js/bootstrap-helper.js @@ -0,0 +1,4 @@ +export { default as BaseComponent } from 'bootstrap/js/src/base-component.js'; +export { default as EventHandler } from "bootstrap/js/src/dom/event-handler.js"; +export { enableDismissTrigger } from 'bootstrap/js/src/util/component-functions.js'; +export { defineJQueryPlugin } from 'bootstrap/js/src/util/index.js'; diff --git a/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.js b/packages/unity-bootstrap-theme/src/js/calendar.js similarity index 96% rename from packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.js rename to packages/unity-bootstrap-theme/src/js/calendar.js index 415d4b825f..dca9f533d0 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.js +++ b/packages/unity-bootstrap-theme/src/js/calendar.js @@ -1,3 +1,5 @@ +import { EventHandler } from "./bootstrap-helper"; + function initCalendar() { const months = [ 'January', @@ -81,6 +83,11 @@ function initCalendar() { const render = () => { const calendarContainer = document.getElementById('calendar'); + + if (!calendarContainer) { + return; + } + calendarContainer.innerHTML = `

${months[state.month]} ${ state.year @@ -154,4 +161,6 @@ function initCalendar() { showCalendar(0); }; +EventHandler.on(window, 'load.uds.calendar', initCalendar); + export { initCalendar } diff --git a/packages/unity-bootstrap-theme/stories/molecules/cards/ranking-cards.js b/packages/unity-bootstrap-theme/src/js/card-ranking.js similarity index 50% rename from packages/unity-bootstrap-theme/stories/molecules/cards/ranking-cards.js rename to packages/unity-bootstrap-theme/src/js/card-ranking.js index 449f2ae793..d0d1507eeb 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/cards/ranking-cards.js +++ b/packages/unity-bootstrap-theme/src/js/card-ranking.js @@ -1,12 +1,16 @@ +import { EventHandler } from "./bootstrap-helper"; + // method ot handle the custom behavior of the ranking card -function rankingFunc() { +function initRankingCard() { const $infoLayer = document.querySelector(".info-layer"); const $toggleIcon = document.getElementById("dispatch"); - $toggleIcon?.addEventListener("click", function () { + EventHandler.on($toggleIcon, "click", function () { $infoLayer?.classList.toggle("active"); }); }; -export { rankingFunc }; +EventHandler.on(window, 'load.uds.ranking-card', initRankingCard); + +export { initRankingCard }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.js b/packages/unity-bootstrap-theme/src/js/charts-and-graphs.js similarity index 68% rename from packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.js rename to packages/unity-bootstrap-theme/src/js/charts-and-graphs.js index db09b07981..1a9b25bd27 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.js +++ b/packages/unity-bootstrap-theme/src/js/charts-and-graphs.js @@ -1,11 +1,21 @@ -import { Chart, registerables } from 'chart.js'; -Chart.register(...registerables); +import * as Chart from 'chart.js'; + +import { EventHandler } from './bootstrap-helper'; function initChart() { + Chart.Chart.register(...Chart.registerables); + const GRAPH_PERCENTAGE_COMPLETE = 50; + + var ctx = document.getElementById('uds-donut'); + + if (!ctx) { + // id="uds-donut" not found in the DOM. + return; + } + document.getElementById('percentage-display').innerHTML = GRAPH_PERCENTAGE_COMPLETE + '%'; - var ctx = document.getElementById('uds-donut'); const config = { type: 'doughnut', @@ -26,7 +36,9 @@ function initChart() { }, }; - var myChart = new Chart(ctx, config); + var myChart = new Chart.Chart(ctx, config); }; +EventHandler.on(window, 'load.uds.chart', initChart); + export { initChart }; diff --git a/packages/unity-bootstrap-theme/src/js/data-layer.js b/packages/unity-bootstrap-theme/src/js/data-layer.js index d401a3c5bb..bb02e76f43 100644 --- a/packages/unity-bootstrap-theme/src/js/data-layer.js +++ b/packages/unity-bootstrap-theme/src/js/data-layer.js @@ -1,4 +1,5 @@ -(function(){ +import { EventHandler } from "./bootstrap-helper"; + function initDataLayer() { /** * Push events to data layer (Google Analytics) @@ -144,6 +145,23 @@ }); }) ); + + document.querySelectorAll('[data-ga-footer]').forEach((element) => + element.addEventListener('focus', () => { + const args = { + type: element.getAttribute('data-ga-footer-type').toLowerCase(), + section: element.getAttribute('data-ga-footer-section').toLowerCase(), + text: element.getAttribute('data-ga-footer').toLowerCase(), + }; + pushGAEvent({ + event: 'link', + action: 'click', + name: 'onclick', + region: 'footer', + ...args, + }); + }) + ); } /* Function must be initialized after document load @@ -151,4 +169,7 @@ * window.initDataLayer(); */ window.initDataLayer = window.initDataLayer || initDataLayer; -})(); + + EventHandler.on(window, 'load.uds.data-layer', initDataLayer); + +export { initDataLayer }; diff --git a/packages/unity-bootstrap-theme/src/js/global-header.js b/packages/unity-bootstrap-theme/src/js/global-header.js index 54ed350301..c7b231a0c5 100644 --- a/packages/unity-bootstrap-theme/src/js/global-header.js +++ b/packages/unity-bootstrap-theme/src/js/global-header.js @@ -1,4 +1,5 @@ -(function(){ +import { EventHandler } from "./bootstrap-helper"; + const initGlobalHeader = () => { // Scroll state const handleWindowScroll = () => { @@ -9,7 +10,7 @@ : headerEl?.classList.remove('scrolled'); }; - window.addEventListener('scroll', handleWindowScroll); + EventHandler.on(window, 'scroll.uds.header', handleWindowScroll); }; window.initGlobalHeader = window.initGlobalHeader || initGlobalHeader; @@ -18,4 +19,7 @@ * Example: * window.initGlobalHeader(); */ -})(); + + EventHandler.on(window, 'load.uds.global-header', initGlobalHeader); + +export { initGlobalHeader }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes-video.js b/packages/unity-bootstrap-theme/src/js/heroes-video.js similarity index 90% rename from packages/unity-bootstrap-theme/stories/molecules/heroes/heroes-video.js rename to packages/unity-bootstrap-theme/src/js/heroes-video.js index ed33567562..5b60fdff4f 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes-video.js +++ b/packages/unity-bootstrap-theme/src/js/heroes-video.js @@ -1,4 +1,6 @@ -function initVideo() { +import { EventHandler } from "./bootstrap-helper"; + +function initHeroesVideo() { // common selectors const DOM_ELEMENT_VIDEO = "video"; const DOM_ELEMENT_BTN_PLAY = "#playHeroVid"; @@ -50,4 +52,6 @@ function initVideo() { }; -export { initVideo }; +EventHandler.on(window, 'load.uds.heroes-video', initHeroesVideo); + +export { initHeroesVideo }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.js b/packages/unity-bootstrap-theme/src/js/image-parallax.js similarity index 90% rename from packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.js rename to packages/unity-bootstrap-theme/src/js/image-parallax.js index 404a427d40..f36e1befb8 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.js +++ b/packages/unity-bootstrap-theme/src/js/image-parallax.js @@ -1,3 +1,5 @@ +import { EventHandler } from "./bootstrap-helper"; + export const initImageParallax = () => { const MAGIC_PARALLAX_FACTOR = 1.2; @@ -91,7 +93,7 @@ export const initImageParallax = () => { // dataLayer elements focus event listener const elements = document.querySelectorAll('[data-ga-image-parallax]'); elements.forEach((element) => - element.addEventListener('focus', () => { + EventHandler.on(element, 'focus.uds.image-parallax', () => { const args = { section: element .getAttribute('data-ga-image-parallax-section') @@ -103,13 +105,11 @@ export const initImageParallax = () => { ); // Window management - window.addEventListener('DOMContentLoaded', function () { - manage_image_sizes(); - }); + manage_image_sizes(); - window.addEventListener('resize', function () { - manage_image_sizes(); - }); + EventHandler.on(window, 'resize.uds.image-parallax', manage_image_sizes); - window.addEventListener('scroll', scrollHandler); + EventHandler.on(window, 'scroll.uds.image-parallax', scrollHandler); }; + +EventHandler.on(window, 'load.uds.image-parallax', initImageParallax); diff --git a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.js b/packages/unity-bootstrap-theme/src/js/modals.js similarity index 78% rename from packages/unity-bootstrap-theme/stories/atoms/modals/modals.js rename to packages/unity-bootstrap-theme/src/js/modals.js index 44d4128ffb..52a00c961e 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.js +++ b/packages/unity-bootstrap-theme/src/js/modals.js @@ -1,3 +1,5 @@ +import { EventHandler } from "./bootstrap-helper"; + function initModals() { document .getElementById('openModalButton') @@ -12,4 +14,6 @@ function initModals() { }); }; +EventHandler.on(window, 'load.uds.modals', initModals); + export { initModals }; diff --git a/packages/unity-bootstrap-theme/src/js/storybook-data-layer.js b/packages/unity-bootstrap-theme/src/js/storybook-data-layer.js deleted file mode 100644 index 1ef8146689..0000000000 --- a/packages/unity-bootstrap-theme/src/js/storybook-data-layer.js +++ /dev/null @@ -1,5 +0,0 @@ -require('./data-layer.js'); - -const { initDataLayer: googleAnalytics } = window; - -export { googleAnalytics }; diff --git a/packages/unity-bootstrap-theme/src/js/storybook-global-header.js b/packages/unity-bootstrap-theme/src/js/storybook-global-header.js deleted file mode 100644 index f089fe6b4f..0000000000 --- a/packages/unity-bootstrap-theme/src/js/storybook-global-header.js +++ /dev/null @@ -1,5 +0,0 @@ -require('./global-header.js'); - -const { initGlobalHeader } = window; - -export { initGlobalHeader }; diff --git a/packages/unity-bootstrap-theme/src/js/tabbed-panels.js b/packages/unity-bootstrap-theme/src/js/tabbed-panels.js new file mode 100644 index 0000000000..bd8b42ff5a --- /dev/null +++ b/packages/unity-bootstrap-theme/src/js/tabbed-panels.js @@ -0,0 +1,145 @@ +import { EventHandler } from "./bootstrap-helper"; + +function initTabbedPanels() { + ("use strict"); + + const DOM_ELEMENT_A = "a"; + const DOM_ELEMENT_BUTTON = "button"; + const DOM_ELEMENT_NAV_TABS = ".nav-tabs"; + const DOM_ELEMENT_NAV_ITEM = ".nav-item"; + const DOM_ELEMENT_UDS_TABBED_PANELS = ".uds-tabbed-panels"; + const DOM_ELEMENT_SCROLL_CONTROL_PREV = ".scroll-control-prev"; + const DOM_ELEMENT_SCROLL_CONTROL_NEXT = ".scroll-control-next"; + const EVENT_CLICK = "click"; + const EVENT_SCROLL = "scroll"; + const EVENT_FOCUS = "focus"; + const CSS_DISPLAY_NONE = "none"; + const CSS_DISPLAY_BLOCK = "block"; + const scrollTollerance = 10; + const LG_BREAKPOINT = 992; + + // helpers functions + const setButtonsCompatibility = e => { + const targets = [DOM_ELEMENT_A, DOM_ELEMENT_BUTTON]; + if (targets.includes(e.target.localName)) { + e.target.focus(); + } + }; + + document + .querySelectorAll(DOM_ELEMENT_UDS_TABBED_PANELS) + .forEach(parentContainer => { + const parentNav = parentContainer.querySelector(DOM_ELEMENT_NAV_TABS); + const navItems = parentContainer.querySelectorAll(DOM_ELEMENT_NAV_ITEM); + const prevButton = parentContainer.querySelector( + DOM_ELEMENT_SCROLL_CONTROL_PREV + ); + const nextButton = parentContainer.querySelector( + DOM_ELEMENT_SCROLL_CONTROL_NEXT + ); + let scrollPosition = 0; + + parentContainer.addEventListener(EVENT_CLICK, function (e) { + setButtonsCompatibility(e); + }); + + const slideNav = (clicked, e, direction) => { + e.preventDefault(); + + // get left value to interact with scroll + const rawLeftValue = getComputedStyle(parentNav).left; + const sanitizedLeftValue = rawLeftValue.replace("px", ""); + let scrollOffset = parseInt(sanitizedLeftValue, 10); + + if (direction === 1 && scrollPosition > 0) { + scrollPosition -= 1; + } + if (scrollPosition < navItems.length - 1 && direction == -1) { + scrollPosition += 1; + } + parentNav.dataset.scrollPosition = scrollPosition; + + scrollOffset = 0; + for (var i = 0; i < scrollPosition; i++) { + scrollOffset += + navItems[i].offsetWidth + + parseInt(getComputedStyle(navItems[i]).marginLeft, 10) + + parseInt(getComputedStyle(navItems[i]).marginRight, 10); + } + + // set the position of the scroll of the .nav-tabs element + parentNav.scrollLeft = scrollOffset; + setControlVisibility(clicked, scrollOffset); + }; + + const setControlVisibility = (clicked, scrollOffset) => { + const tabPosition = parentNav.scrollWidth - scrollOffset; + + // hide or show the scroll buttons based on the scroll position + if (scrollPosition == 0) { + prevButton.style.display = CSS_DISPLAY_NONE; + } else { + prevButton.style.display = CSS_DISPLAY_BLOCK; + } + if (tabPosition <= parentContainer.offsetWidth) { + nextButton.style.display = CSS_DISPLAY_NONE; + } else { + nextButton.style.display = CSS_DISPLAY_BLOCK; + } + }; + + parentNav.addEventListener(EVENT_SCROLL, event => { + const scrollPos = event.target.scrollLeft; + const atFarRight = + parentNav.offsetWidth + scrollPos + scrollTollerance >= + parentNav.scrollWidth; + prevButton.style.display = + scrollPos < scrollTollerance ? CSS_DISPLAY_NONE : CSS_DISPLAY_BLOCK; + nextButton.style.display = atFarRight + ? CSS_DISPLAY_NONE + : CSS_DISPLAY_BLOCK; + }); + // }); + + // handle focus event for tabs titles + navItems.forEach(tabTitle => { + tabTitle.addEventListener(EVENT_FOCUS, function (e) { + tabTitle.scrollIntoView(); + }); + }); + + // click of the next button + nextButton.addEventListener(EVENT_CLICK, function (e) { + if (window.innerWidth > LG_BREAKPOINT) { + slideNav(this, e, -1); + } + }); + + // click of the prev button + prevButton.addEventListener(EVENT_CLICK, function (e) { + if (window.innerWidth > LG_BREAKPOINT) { + slideNav(this, e, 1); + } + }); + + // hide prev button on load + + prevButton.style.display = CSS_DISPLAY_NONE; + + // width of all tabs + const navTabWidth = parentNav.scrollWidth; + + // width of the parent element + const udsTabbedPanelsWidth = parentContainer.offsetWidth; + + if (navTabWidth <= udsTabbedPanelsWidth) { + nextButton.style.display = CSS_DISPLAY_NONE; + } + }); +} + +EventHandler.on(window, 'load.uds.tabs', initTabbedPanels); + +// window.addEventListener("load.uds.tabs", initTabs, true); + +export { initTabbedPanels }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/tables/tables.js b/packages/unity-bootstrap-theme/src/js/tables.js similarity index 77% rename from packages/unity-bootstrap-theme/stories/molecules/tables/tables.js rename to packages/unity-bootstrap-theme/src/js/tables.js index 19edab9e2e..6eeb122b29 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/tables/tables.js +++ b/packages/unity-bootstrap-theme/src/js/tables.js @@ -4,14 +4,17 @@ * Fixed table should display scroll buttons when hovering over scrollable * portion of table, and hide them when hovering over fixed column or when * mouse exits table. - * - * The scroll buttons must be outside the table container, within the table wrapper, - * due to the absolute positioning requirements. Because the table scrolls, - * if they were to be absolutely positioned in the same container as the table, - * they would scroll with it. - */ +* +* The scroll buttons must be outside the table container, within the table wrapper, +* due to the absolute positioning requirements. Because the table scrolls, +* if they were to be absolutely positioned in the same container as the table, +* they would scroll with it. +*/ -function initializeFixedTable() { +import { EventHandler } from "./bootstrap-helper"; + + +function initFixedTable() { function setPreButtonPosition() { const wrapperSelector = '.uds-table-fixed-wrapper'; const tableSelector = '.uds-table.uds-table-fixed table'; @@ -43,12 +46,12 @@ function initializeFixedTable() { const nextButton = wrapper.querySelector(nextScrollSelector); ['click', 'focus'].forEach((eventName) => { - prevButton.addEventListener(eventName, function () { + EventHandler.on(prevButton, eventName, function () { /* Scroll can't go beyond it's bounds, it won't go lower than 0 */ container.scrollLeft -= 100; }); - nextButton.addEventListener(eventName, function () { + EventHandler.on(nextButton, eventName, function () { container.scrollLeft += 100; }); }); @@ -66,9 +69,11 @@ function initializeFixedTable() { } setPreButtonPosition(); setButtonLiListeners(); - window.addEventListener('resize', function () { + EventHandler.on(window, 'resize', function () { debounce(setPreButtonPosition, 100)(); }); } -export { initializeFixedTable }; +EventHandler.on(window, 'load.uds.fixed-table', initFixedTable); + +export { initFixedTable }; diff --git a/packages/unity-bootstrap-theme/src/js/unity-bootstrap.js b/packages/unity-bootstrap-theme/src/js/unity-bootstrap.js new file mode 100644 index 0000000000..2a1fcf0d65 --- /dev/null +++ b/packages/unity-bootstrap-theme/src/js/unity-bootstrap.js @@ -0,0 +1,33 @@ +// import Banner from "./banner.js"; +import { initAnchorMenu } from "./anchor-menu.js"; +import { initBlockquoteAnimation } from "./blockquote-animated.js"; +import { initCalendar } from "./calendar.js"; +import { initRankingCard } from "./card-ranking.js"; +import { initChart } from "./charts-and-graphs.js"; +import { initDataLayer } from "./data-layer.js"; +import { initGlobalHeader } from "./global-header.js"; +import { initHeroesVideo } from "./heroes-video.js"; +import { initImageParallax } from "./image-parallax.js"; +import { initModals } from "./modals.js"; +import { initTabbedPanels } from "./tabbed-panels.js"; +import { initFixedTable } from "./tables.js"; +import { initVideo } from "./video.js"; + +const unityBootstrap = { + // Banner, // code updated to use bootstrap alert so we don't need this + initAnchorMenu, + initBlockquoteAnimation, + initCalendar, + initChart, + initDataLayer, + initFixedTable, + initGlobalHeader, + initHeroesVideo, + initImageParallax, + initModals, + initRankingCard, + initTabbedPanels, + initVideo, +} + +export default unityBootstrap; diff --git a/packages/unity-bootstrap-theme/stories/atoms/video/video.js b/packages/unity-bootstrap-theme/src/js/video.js similarity index 94% rename from packages/unity-bootstrap-theme/stories/atoms/video/video.js rename to packages/unity-bootstrap-theme/src/js/video.js index e3a7a495b8..866a19252a 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/video/video.js +++ b/packages/unity-bootstrap-theme/src/js/video.js @@ -1,3 +1,4 @@ +import { EventHandler } from "./bootstrap-helper"; function initVideo() { // constants @@ -57,4 +58,6 @@ function initVideo() { }; +EventHandler.on(window, 'load.uds.video', initVideo); + export { initVideo }; diff --git a/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-header-footer.scss b/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-header-footer.scss new file mode 100644 index 0000000000..2ec330b025 --- /dev/null +++ b/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-header-footer.scss @@ -0,0 +1,12 @@ +@import "shared"; + +// 8. Add additional custom code here + +// import bootstrap core +// @import "bootstrap/scss/bootstrap"; +// css Bootstrap doesn't have variables for +// @import "unity-bootstrap-theme-extends"; +@import 'extends/global-header'; +@import 'extends/globalfooter'; + +@import 'end-style'; diff --git a/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss b/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss index 1faa1ce120..398cb6aab8 100755 --- a/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss +++ b/packages/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss @@ -1,7 +1,6 @@ $add_end_styles: false; @import 'unity-bootstrap-theme'; -@import 'unity-bootstrap-header'; -@import 'unity-bootstrap-footer'; +@import 'unity-bootstrap-header-footer'; $add_end_styles: true; @import 'end-style'; diff --git a/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.examples.stories.js index c6956bd1ed..ea919af2fb 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/anchor-menu/anchor-menu.examples.stories.js @@ -1,33 +1,189 @@ import React from "react"; -import { defaultDecorator } from "../../../../../shared/components/Layout"; +import { imageName } from "../../../../../shared/assets"; +import { htmlRootDecorator } from "../../../../../shared/components/Layout"; +import {getLoremSentences} from "../../../../../shared/constants/strings"; export default { title: "Atoms/Anchor Menu", - decorators: [ defaultDecorator ], - parameters: { - controls: { disable: true }, - docs: { - description: { - component: `This example has moved to unity-react-core. - -React components and HTML templates are now located in a shared space to ensure consistency between the React and Bootstrap usages. - `, - }, + parameters: { + initFunc: { + disable: false, + }, + header: { + forced: true, }, - } + controls: { disable: true }, + }, + decorators: [htmlRootDecorator], }; - - -export const AnchorMenu = { - render: () =>

- Follow the link to view the{" "} - - anchor menu - {" "} - example in @asu/unity-react-core. -

+let loremOffset = 0; +export const AnchorMenu = () => { + return ( + <> +
+
+ Sample placeholder image. +

+ Gettysburg Address +

+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. +

+
+
+ + Read the 13th Amendment + +
+
+ +
+
+
+
+
+

{getLoremSentences(40, loremOffset++ * 3)}

+

{getLoremSentences(30, loremOffset++ * 3)}

+

{getLoremSentences(20, loremOffset++ * 3)}

+

{getLoremSentences(40, loremOffset++ * 3)}

+
+
+

first

+

First. {getLoremSentences(40, loremOffset++ * 3)}

+

First. {getLoremSentences(50, loremOffset++ * 3)}

+

First. {getLoremSentences(10, loremOffset++ * 3)}

+
+
+

second

+

Second. {getLoremSentences(20, loremOffset++ * 3)}

+

Second. {getLoremSentences(50, loremOffset++ * 3)}

+

Second. {getLoremSentences(60, loremOffset++ * 3)}

+
+
+

third

+

Third. {getLoremSentences(20, loremOffset++ * 3)}

+

Third. {getLoremSentences(10, loremOffset++ * 3)}

+

Third. {getLoremSentences(40, loremOffset++ * 3)}

+

Third. {getLoremSentences(10, loremOffset++ * 3)}

+
+
+

fourth

+

Fourth. {getLoremSentences(70, loremOffset++ * 3)}

+

Fourth. {getLoremSentences(40, loremOffset++ * 3)}

+
+
+

fifth

+

Fifth. {getLoremSentences(40, loremOffset++ * 3)}

+

Fifth. {getLoremSentences(10, loremOffset++ * 3)}

+

Fifth. {getLoremSentences(30, loremOffset++ * 3)}

+

Fifth. {getLoremSentences(20, loremOffset++ * 3)}

+
+
+
+
+
+
+ + ); }; diff --git a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.examples.stories.js index 0e3006b417..bff33af586 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.examples.stories.js @@ -219,7 +219,11 @@ export const BlockquoteAnimated = () => ( ); - +BlockquoteAnimated.parameters = { + initFunc: { + disable: false, + }, +}; export const TestimonialsNoImage = () => ( <>
diff --git a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.templates.stories.js b/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.templates.stories.js index bb30006ef8..e81016be34 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/blockquote/blockquote.templates.stories.js @@ -2,15 +2,10 @@ import React from "react"; import { imageName } from "../../../../../shared/assets"; import { defaultDecorator } from "../../../../../shared/components/Layout.js"; -import { initializeBlockquoteAnimation as doAnimate } from "./blockquote-animated"; export default { title: "Atoms/Blockquotes and Testimonials/Templates", parameters: { - initFunc: { - disable: false, - code: doAnimate, - }, }, args: { type: "On White", diff --git a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/modals/modals.examples.stories.js index dac601a545..7f04bf84ee 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/modals/modals.examples.stories.js @@ -1,13 +1,10 @@ import React from "react"; -import { initModals as initFunc } from "./modals.js"; - export default { title: "Atoms/Modals/Examples", parameters: { initFunc: { disable: false, - code: initFunc }, header: { forced: true, diff --git a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.templates.stories.js b/packages/unity-bootstrap-theme/stories/atoms/modals/modals.templates.stories.js index 1e42a5ffa2..5b7fb7cab3 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/modals/modals.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/modals/modals.templates.stories.js @@ -1,7 +1,6 @@ import React from "react"; import { defaultDecorator } from "../../../../../shared/components/Layout"; -import { initModals as initFunc } from "./modals.js"; export default { title: "Atoms/Modals/Templates", @@ -9,14 +8,13 @@ export default { parameters: { initFunc: { disable: false, - code: initFunc }, header: { forced: true, }, footer: { forced: true, - } + }, }, args: {open: true}, argTypes: { diff --git a/packages/unity-bootstrap-theme/stories/atoms/video/video.templates.stories.js b/packages/unity-bootstrap-theme/stories/atoms/video/video.templates.stories.js index d795f44e27..0d2879f732 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/video/video.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/video/video.templates.stories.js @@ -2,7 +2,6 @@ import React from "react"; // @ts-ignore import stockVideo from "./stock-video-person-drawing.mp4"; -import { initVideo as initFunc } from "./video"; export default { @@ -17,7 +16,6 @@ export default { parameters: { initFunc: { disable: false, - code: initFunc }, }, }; diff --git a/packages/unity-bootstrap-theme/stories/docs/GetStarted/get-started.stories.mdx b/packages/unity-bootstrap-theme/stories/docs/GetStarted/get-started.stories.mdx index d76d45d785..41f6e22200 100755 --- a/packages/unity-bootstrap-theme/stories/docs/GetStarted/get-started.stories.mdx +++ b/packages/unity-bootstrap-theme/stories/docs/GetStarted/get-started.stories.mdx @@ -17,12 +17,39 @@ This is the Storybook reference site for the Unity Bootstrap theme. Browse the d ## ❯ Including Unity in your project -We bundle the necessary CSS and Javascript in the `dist/` folder. There are more options so you don't have to include CSS you don't use. - -* `dist/unity-bootstrap-theme.bundle.css` - base theme with header and footer CSS -* `dist/unity-bootstrap-theme.css` - base theme without header and footer CSS -* `dist/unity-bootstrap-header.css` (optional) - header CSS only - don't use if using the Unity `component-header` React component -* `dist/unity-bootstrap-footer.css` (optional) - footer CSS only - don't use if using the Unity `component-footer` React component +We bundle the necessary CSS and Javascript in the `dist/` folder. There are more options so you don't have to include CSS you don't use. +### CSS +* `dist/css/unity-bootstrap-theme.bundle.css` - base theme with header and footer CSS +* `dist/css/unity-bootstrap-theme.css` - base theme without header and footer CSS +* `dist/css/unity-bootstrap-header-footer.css` (optional) - header and footer CSS only - don't use if using the Unity `component-header-footer` React component + +### Javascript +All you need to do is include the script on your page, the scripts will execute with the window load event. +We provide 3 formats ("UMD", "CJS", "ES"). + +* Unity File Formats + * `dist/js/unity-bootstrap.umd.js` (universal) + * creates a global variable `unityBootstrap` if you need to manaully call the init function ex: `unityBootstrap.initAnchorMenu()` + * `dist/js/unity-bootstrap.cjs.js` (common js) + * `dist/js/unity-bootstrap.es.js` (module) +* Vendor Files + * `dist/js/bootstrap.bundle.min.js` (unaltered bootstrap version) + * `dist/js/chart.min.js` (only needed if you are using the donut chart) + +* Exports: + * `initAnchorMenu` + * `initBlockquoteAnimation` + * `initCalendar` + * `initChart` + * `initDataLayer` + * `initFixedTable` + * `initGlobalHeader` + * `initHeroesVideo` + * `initImageParallax` + * `initModals` + * `initRankingCard` + * `initTabbedPanels` + * `initVideo` ## ❯ How to use the Unity Storybook reference site diff --git a/packages/unity-bootstrap-theme/stories/molecules/banners/banner.js b/packages/unity-bootstrap-theme/stories/molecules/banners/banner.js deleted file mode 100644 index b90c378843..0000000000 --- a/packages/unity-bootstrap-theme/stories/molecules/banners/banner.js +++ /dev/null @@ -1,11 +0,0 @@ -const initBanner = () => { - const closeButtons = document.querySelectorAll('.banner-close button'); - closeButtons.forEach((button) => - button.addEventListener('click', () => { - const bannerToClose = button.parentElement.parentElement.parentElement; - bannerToClose.classList.add('hidden-banner'); - }) - ); -}; - -export { initBanner }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/banners/banners.templates.stories.js b/packages/unity-bootstrap-theme/stories/molecules/banners/banners.templates.stories.js index d4b783311f..6678d53d0f 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/banners/banners.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/banners/banners.templates.stories.js @@ -1,19 +1,12 @@ import React from "react"; import { fullLayoutDecorator } from "../../../../../shared/components/Layout"; -import { initBanner as initFunc } from "./banner"; export default { title: "Molecules/Banners/Templates", decorators: [ fullLayoutDecorator ], - parameters: { - initFunc: { - code: initFunc, - disable: false, - }, - }, argTypes: { color: { name: "Color", @@ -32,11 +25,16 @@ export default { args: { color: "Orange", }, + parameters: { + initFunc: { + disable: false, + }, + }, }; export const Banner = ({color}) => { return ( -
+
@@ -67,6 +65,7 @@ export const Banner = ({color}) => { type="button" className="btn btn-circle btn-circle-alt-black close" aria-label="Close" + data-bs-dismiss="alert" > diff --git a/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.templates.stories.js b/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.templates.stories.js index 08572e5917..9300680f28 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/calendar/calendar.templates.stories.js @@ -1,14 +1,12 @@ import React from "react"; import { defaultDecorator } from "../../../../../shared/components/Layout"; -import { initCalendar as initFunc } from "./calendar"; export default { title: "Molecules/Calendar/Templates", decorators: [ defaultDecorator ], parameters: { initFunc: { - code: initFunc, disable: false, }, controls: { disable: true } diff --git a/packages/unity-bootstrap-theme/stories/molecules/cards/cards.examples.stories.js b/packages/unity-bootstrap-theme/stories/molecules/cards/cards.examples.stories.js index 3445109adb..465ca2f5ac 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/cards/cards.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/cards/cards.examples.stories.js @@ -3,7 +3,6 @@ import React from "react"; import { defaultDecorator, htmlRootDecorator } from "../../../../../shared/components/Layout"; import cardsImage from "./cards-image.jpg"; import { horizontalCardsMap as horizontalCard } from "./cardVariations.js"; -import { rankingFunc as initRankFunc } from "./ranking-cards.js"; export default { title: "Molecules/Cards/Examples", @@ -1533,10 +1532,9 @@ export const RankingCardLarge = () => ( ); RankingCardLarge.parameters = { initFunc: { - code: initRankFunc, disable: false, }, -} +}; RankingCardLarge.decorators = [ defaultDecorator ]; export const RankingCardSmall = () => ( @@ -1607,8 +1605,7 @@ export const RankingCardSmall = () => ( ); RankingCardSmall.parameters = { initFunc: { - code: initRankFunc, disable: false, }, -} +}; RankingCardSmall.decorators = [ defaultDecorator ]; diff --git a/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.templates.stories.js b/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.templates.stories.js index 90743c6124..f05f97eca8 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/charts-and-graphs/charts-and-graphs.templates.stories.js @@ -1,14 +1,12 @@ import React from "react"; import { defaultDecorator } from "../../../../../shared/components/Layout"; -import { initChart } from "./charts-and-graphs"; export default { title: "Molecules/Charts And Graphs/Templates", decorators: [ defaultDecorator ], parameters: { initFunc: { - code: initChart, disable: false, }, controls: { disable: true } diff --git a/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent-full-screen.stories.js b/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent-full-screen.stories.js index 6af8fd3a30..cca5da4139 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent-full-screen.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent-full-screen.stories.js @@ -1,11 +1,9 @@ import React, { useEffect } from "react"; -import { forceReloadOfStory } from "../../../.storybook/decorators"; import { allCookieConsentJS } from "../../../src/js/cookie-consent-full-screen"; import "../../../src/css/cookie-consent-full-screen.css"; export default { title: "Molecules/Cookie Consent", - decorators: [forceReloadOfStory], parameters: { controls: { disable: true } }, }; export const FullScreenBannerCookieConsent = () => { diff --git a/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent.stories.js b/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent.stories.js index a41fbb788c..3ee99bb9d4 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/cookie-consent/cookie-consent.stories.js @@ -1,11 +1,10 @@ import React, { useEffect } from "react"; import { allCookieConsentJS } from "../../../src/js/cookie-consent"; -import { forceReloadOfStory } from "../../../.storybook/decorators"; + import "../../../src/css/cookie-consent.css"; export default { title: "Molecules/Cookie Consent", - decorators: [forceReloadOfStory], parameters: { controls: { disable: true } }, }; export const MaxWidthCookieConsent = () => { diff --git a/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes.examples.stories.js b/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes.examples.stories.js index 2c4352b8b5..d9ee2ae957 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/heroes/heroes.examples.stories.js @@ -3,7 +3,6 @@ import React from "react"; import { fullLayoutDecorator } from "../../../../../shared/components/Layout.js"; import stockVideo from "../../atoms/video/stock-video-person-drawing.mp4"; import cardsImage from "../cards/cards-image.jpg"; -import { initVideo as initFunc } from "./heroes-video"; export default { title: "Molecules/Heroes/Examples", @@ -242,10 +241,9 @@ export const HeroVideo = () => (
); + HeroVideo.parameters = { initFunc: { - code: initFunc, disable: false, }, }; - diff --git a/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.examples.stories.js b/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.examples.stories.js index 34432827f3..732f327779 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/image-parallax/image-parallax.examples.stories.js @@ -2,14 +2,12 @@ import React from "react"; import { imageName } from "../../../../../shared/assets"; import { defaultDecorator } from "../../../../../shared/components/Layout"; -import { initImageParallax as initFunc } from "./image-parallax"; export default { title: "Molecules/Image Parallax/Examples", decorators: [ defaultDecorator ], parameters: { initFunc: { - code: initFunc, disable: false, }, controls: { disable: true } diff --git a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js b/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js deleted file mode 100644 index 74c1adf8e2..0000000000 --- a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js +++ /dev/null @@ -1,168 +0,0 @@ -function initTabs() { - "use strict"; - const DOM_ELEMENT_A = "a"; - const DOM_ELEMENT_BUTTON = "button"; - const DOM_ELEMENT_NAV_TABS = ".nav-tabs"; - const DOM_ELEMENT_NAV_ITEM = ".nav-item"; - const DOM_ELEMENT_UDS_TABBED_PANELS = ".uds-tabbed-panels"; - const DOM_ELEMENT_SCROLL_CONTROL_PREV = ".scroll-control-prev"; - const DOM_ELEMENT_SCROLL_CONTROL_NEXT = ".scroll-control-next"; - const EVENT_LOAD = "load"; - const EVENT_CLICK = "click"; - const EVENT_SCROLL = "scroll"; - const CSS_DISPLAY_NONE = "none"; - const CSS_DISPLAY_BLOCK = "block"; - const magicalNumberThree = 3; - const LG_BREAKPOINT = 992; - - // helpers functions - const setButtonsCompatibility = e => { - const targets = [DOM_ELEMENT_A, DOM_ELEMENT_BUTTON]; - if (targets.includes(e.target.localName)) { - e.target.focus(); - } - }; - - const slideNav = (clicked, e, direction) => { - e.preventDefault(); - const parentNav = Array.from(clicked.parentElement.children).filter(child => - child.classList.contains("nav-tabs") - ); - - let scrollPosition = parseInt(parentNav[0].dataset.scrollPosition, 10); - - const navItems = Array.from( - parentNav[0].querySelectorAll(DOM_ELEMENT_NAV_ITEM) - ); - - // get left value to interact with scroll - const rawLeftValue = getComputedStyle(parentNav[0]).left; - const sanitizedLeftValue = rawLeftValue.replace("px", ""); - let scrollOffset = parseInt(sanitizedLeftValue, 10); - - if (direction === 1 && scrollPosition > 0) { - scrollPosition -= 1; - } - if (scrollPosition < navItems.length - 1 && direction == -1) { - scrollPosition += 1; - } - parentNav[0].dataset.scrollPosition = scrollPosition; - - scrollOffset = 0; - for (var i = 0; i < scrollPosition; i++) { - scrollOffset += - navItems[i].offsetWidth + - parseInt(getComputedStyle(navItems[i]).marginLeft, 10) + - parseInt(getComputedStyle(navItems[i]).marginRight, 10); - } - - // set the position of the scroll of the .nav-tabs element - parentNav[0].scrollLeft = scrollOffset; - setControlVisibility(clicked, scrollOffset); - }; - - const setControlVisibility = (clicked, scrollOffset) => { - // select the nearest ancestor with the class ".uds-tabbed-panels". - const parentContainer = clicked.closest(DOM_ELEMENT_UDS_TABBED_PANELS); - // select the sibling parent elements of the clicked element. - const parentNav = parentContainer.querySelector(DOM_ELEMENT_NAV_TABS); - - // get the value of the data-scroll-position attribute and make sure it is an integer - const scrollPosition = parseInt(parentNav.dataset.scrollPosition, 10); - const tabPosition = parentNav.scrollWidth - scrollOffset; - - // hide or show the scroll buttons based on the scroll position - if (scrollPosition == 0) { - parentContainer.querySelector( - DOM_ELEMENT_SCROLL_CONTROL_PREV - ).style.display = CSS_DISPLAY_NONE; - } else { - parentContainer.querySelector( - DOM_ELEMENT_SCROLL_CONTROL_PREV - ).style.display = CSS_DISPLAY_BLOCK; - } - if (tabPosition <= parentContainer.offsetWidth) { - parentContainer.querySelector( - DOM_ELEMENT_SCROLL_CONTROL_NEXT - ).style.display = CSS_DISPLAY_NONE; - } else { - parentContainer.querySelector( - DOM_ELEMENT_SCROLL_CONTROL_NEXT - ).style.display = CSS_DISPLAY_BLOCK; - } - }; - - // wait to load the page and all resources before initializing - window.addEventListener(EVENT_LOAD, function windowLoad() { - window.removeEventListener(EVENT_LOAD, windowLoad) - // wait to DOM content is loaded before run these scripts - document.addEventListener(EVENT_CLICK, function (e) { - setButtonsCompatibility(e); - }); - - // handle focus event for tabs titles - const navItems = document.querySelectorAll(DOM_ELEMENT_NAV_ITEM); - navItems.forEach(tabTitle => { - tabTitle.addEventListener("focus", function (e) { - tabTitle.scrollIntoView(); - }) - }) - - // handle scroll for tabs titles - document.querySelectorAll(DOM_ELEMENT_UDS_TABBED_PANELS).forEach(item => { - const nav = item.querySelector(DOM_ELEMENT_NAV_TABS); - nav.addEventListener(EVENT_SCROLL, event => { - const scrollPos = event.target.scrollLeft; - const prevButton = item.querySelector(DOM_ELEMENT_SCROLL_CONTROL_PREV); - const nextButton = item.querySelector(DOM_ELEMENT_SCROLL_CONTROL_NEXT); - const atFarRight = - nav.offsetWidth + scrollPos + magicalNumberThree >= nav.scrollWidth; - prevButton.style.display = - scrollPos === 0 ? CSS_DISPLAY_NONE : CSS_DISPLAY_BLOCK; - nextButton.style.display = atFarRight - ? CSS_DISPLAY_NONE - : CSS_DISPLAY_BLOCK; - }); - }); - - // click of the next button - document - .querySelector(DOM_ELEMENT_SCROLL_CONTROL_NEXT) - .addEventListener(EVENT_CLICK, function (e) { - if (window.innerWidth > LG_BREAKPOINT) { - slideNav(this, e, -1); - } - }); - - // click of the prev button - document - .querySelector(DOM_ELEMENT_SCROLL_CONTROL_PREV) - .addEventListener(EVENT_CLICK, function (e) { - if (window.innerWidth > LG_BREAKPOINT) { - slideNav(this, e, 1); - } - }); - - // hide prev button on load - document.querySelector( - `${DOM_ELEMENT_UDS_TABBED_PANELS} ${DOM_ELEMENT_SCROLL_CONTROL_PREV}` - ).style.display = CSS_DISPLAY_NONE; - - // width of all tabs - const navTabWidth = - document.querySelector(DOM_ELEMENT_NAV_TABS).scrollWidth; - - // width of the parent element - const udsTabbedPanelsWidth = document.querySelector( - DOM_ELEMENT_UDS_TABBED_PANELS - ).offsetWidth; - - if (navTabWidth <= udsTabbedPanelsWidth) { - document.querySelector( - `${DOM_ELEMENT_UDS_TABBED_PANELS} ${DOM_ELEMENT_SCROLL_CONTROL_NEXT}` - ).style.display = CSS_DISPLAY_NONE; - } - }); -}; - -export {initTabs} diff --git a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js.stories.mdx b/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js.stories.mdx deleted file mode 100644 index 342e4a8540..0000000000 --- a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.js.stories.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import { Meta, Source } from "@storybook/blocks"; -import { initTabs as initFunc } from "./tabbed-panels.js" - - - -## Tabbed Panels - - diff --git a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.templates.stories.js b/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.templates.stories.js index 3509c381f6..9ce3659a71 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/tabbed-panels/tabbed-panels.templates.stories.js @@ -15,8 +15,6 @@ const extraOptions = { }, }; -import { initTabs as initFunc } from "./tabbed-panels.js"; - export const TabbedPanels = (args) => { return (
@@ -251,14 +249,13 @@ export const TabbedPanels = (args) => { ); } -TabbedPanels.parameters = { - initFunc: { - disable: false, - code: initFunc - } -} export default { title: "Molecules/Tabbed Panels", component: TabbedPanels, - argTypes: {...extraOptions} + argTypes: {...extraOptions}, + parameters: { + initFunc: { + disable: false, + }, + }, }; diff --git a/packages/unity-bootstrap-theme/stories/molecules/tables/tables.templates.stories.js b/packages/unity-bootstrap-theme/stories/molecules/tables/tables.templates.stories.js index f11a4e1f85..e213b36dbd 100644 --- a/packages/unity-bootstrap-theme/stories/molecules/tables/tables.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/molecules/tables/tables.templates.stories.js @@ -1,7 +1,6 @@ import React from "react"; import { defaultDecorator } from "../../../../../shared/components/Layout"; -import {initializeFixedTable as initFunc} from "./tables"; export default { title: "Molecules/Tables/Templates", @@ -19,12 +18,6 @@ export default { fixed: false, columns: 5, }, - parameters: { - initFunc: { - code: initFunc, - disable: false, - }, - } }; const makingUpFakeNumbers = (a,b,c) => Math.round(a*(b+c)).toLocaleString('en-US'); @@ -132,3 +125,9 @@ FixedComponent.args = { fixed: true, columns: 7, } +FixedComponent.parameters = { + initFunc: { + disable: false, + }, +}; + diff --git a/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.examples.stories.js b/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.examples.stories.js index 0bbd3efa33..b01700771f 100644 --- a/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.examples.stories.js @@ -1,5 +1,4 @@ import { htmlRootDecorator } from "../../../../../shared/components/Layout.js"; -import { initFooterGA as initFunc } from "./global-footer"; import { GlobalElementsOnly, ZeroColumns, @@ -16,12 +15,11 @@ export default { title: "Organisms/Global Footer/Examples", argTypes: {}, parameters: { - footer: { - disable: true - }, initFunc: { disable: false, - code: initFunc + }, + footer: { + disable: true }, controls: { disable: true } }, diff --git a/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.js b/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.js deleted file mode 100644 index c847b32f49..0000000000 --- a/packages/unity-bootstrap-theme/stories/organisms/global-footer/global-footer.js +++ /dev/null @@ -1,25 +0,0 @@ -export const initFooterGA = () => { - const pushFooterGAEvent = (args) => { - const { dataLayer } = window; - const event = { - event: 'link', - action: 'click', - name: 'onclick', - region: 'footer', - ...args, - }; - if (dataLayer) dataLayer.push(event); - }; - - const elements = document.querySelectorAll('[data-ga-footer]'); - elements.forEach((element) => - element.addEventListener('focus', () => { - const args = { - type: element.getAttribute('data-ga-footer-type').toLowerCase(), - section: element.getAttribute('data-ga-footer-section').toLowerCase(), - text: element.getAttribute('data-ga-footer').toLowerCase(), - }; - pushFooterGAEvent(args); - }) - ); -}; diff --git a/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.examples.stories.js b/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.examples.stories.js index 3f3cced52b..6b38997e51 100644 --- a/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.examples.stories.js @@ -8,12 +8,15 @@ import { NoNavigationAndWithButtons, ScrolledState, Partner, -} from "./global-header.templates.js"; +} from "./global-header.templates.jsx"; export default { title: "Organisms/Global Header/Examples", decorators: [ htmlRootDecorator ], parameters: { + initFunc: { + disable: false, + }, header: { disable: true }, diff --git a/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.templates.js b/packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.templates.jsx similarity index 100% rename from packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.templates.js rename to packages/unity-bootstrap-theme/stories/organisms/global-header/global-header.templates.jsx diff --git a/packages/unity-bootstrap-theme/vite.config.bundle.js b/packages/unity-bootstrap-theme/vite.config.bundle.js new file mode 100644 index 0000000000..7bd3029316 --- /dev/null +++ b/packages/unity-bootstrap-theme/vite.config.bundle.js @@ -0,0 +1,53 @@ +import fs from "fs"; +import { resolve } from "path"; +import { defineConfig } from "vite"; + +import baseConfig from "./vite.config.js"; + +export default defineConfig( + (/** @type {import('vite').ConfigEnv} */ _) => { + + return { + ...baseConfig, + plugins: [ + { + name: "copy", + closeBundle() { + fs.mkdirSync(resolve(__dirname, "dist/js"), { + recursive: true, + }); + fs.copyFileSync( + resolve( + __dirname, + "../../node_modules", + "bootstrap/dist/js/bootstrap.bundle.min.js" + ), + resolve(__dirname, "dist/js/bootstrap.bundle.min.js") + ); + fs.copyFileSync( + resolve( + __dirname, + "../../node_modules", + "chart.js/dist/chart.min.js" + ), + resolve(__dirname, "dist/js/chart.min.js") + ); + }, + }, + ], + build: { + ...baseConfig.build, + emptyOutDir: false, + lib: { + entry: resolve(__dirname, "src/js/unity-bootstrap.js"), + formats: ["umd", "cjs", "es"], + name: "unityBootstrap", + }, + rollupOptions: { + ...baseConfig.build.rollupOptions, + input: undefined, + }, + }, + }; + } +); diff --git a/packages/unity-bootstrap-theme/vite.config.js b/packages/unity-bootstrap-theme/vite.config.js index 658cbece41..ed3e09081b 100644 --- a/packages/unity-bootstrap-theme/vite.config.js +++ b/packages/unity-bootstrap-theme/vite.config.js @@ -1,12 +1,18 @@ import react from "@vitejs/plugin-react"; -import path, { resolve } from "path"; -import fs from "fs"; +import { resolve } from "path"; import { defineConfig, transformWithEsbuild } from "vite"; import pkg from "./package.json"; -/** @typedef {import('vite').UserConfig} UserConfig */ -/** @type {UserConfig} */ +const getName = ({originalFileNames}) => { + // regex matches string after last slash and before the last dot + // e.g. /path/to/file.css -> file + // e.g. /path/to/file.min.css -> file.min + const name = originalFileNames?.at(0)?.match(/\/([^/]*)?\..*$/).at(1) || "[name]"; + return name; +} + +/** @type {import('vite').UserConfig} */ const c = { root: resolve(__dirname), plugins: [ @@ -15,23 +21,12 @@ const c = { name: "treat-js-files-as-jsx", async transform(code, id) { if (!id.match(/stories\/.*\.js$/)) return null; - return transformWithEsbuild(code, id, { loader: "jsx", jsx: "automatic", }); }, }, - { - name: 'copy-bootstrap-umd-to-dist', - // See https://vite.dev/guide/api-plugin#universal-hooks for closeBundle info - closeBundle() { - const srcPath = path.resolve(__dirname, "../../node_modules", 'bootstrap/dist/js/bootstrap.bundle.min.js'); - const destDir = path.resolve(__dirname, 'dist/js/bootstrap.bundle.min.js'); - - fs.copyFileSync(srcPath, destDir); - } - } ], optimizeDeps: { esbuildOptions: { @@ -46,37 +41,22 @@ const c = { sourcemap: true, cssMinify: true, cssCodeSplit: true, - lib: { - entry: [ - resolve(__dirname, "src/scss/unity-bootstrap-theme.bundle.scss"), - resolve(__dirname, "src/scss/unity-bootstrap-theme.scss"), - resolve(__dirname, "src/scss/unity-bootstrap-header.scss"), - resolve(__dirname, "src/scss/unity-bootstrap-footer.scss"), - resolve(__dirname, "src/js/global-header.js"), - resolve(__dirname, "src/js/data-layer.js"), - resolve(__dirname, "../../node_modules/bootstrap/js/index.esm.js"), - ], - - }, outDir: "dist", rollupOptions: { - external: Object.keys(pkg.peerDependencies), + external: [...Object.keys(pkg.peerDependencies), "chart.js"], treeshake: true, + input: [ + resolve(__dirname, "src/scss/unity-bootstrap-theme.bundle.scss"), + resolve(__dirname, "src/scss/unity-bootstrap-theme.scss"), + resolve(__dirname, "src/scss/unity-bootstrap-header-footer.scss"), + ], output: { - entryFileNames: chunkInfo => { - if (chunkInfo.name.includes("index.esm")) { - return "js/bootstrap.bundle.min.[format]"; - } - return "js/[name].[format]"; + globals: { + "chart.js": "Chart", }, - chunkFileNames: "js/[name].[format]", - assetFileNames: (assetInfo) => { - if (assetInfo.originalFileNames && assetInfo.originalFileNames[0].includes("bundle")) { - return "css/unity-bootstrap-theme.bundle.[ext]"; - } - return "css/[name].[ext]"; - }, - format: "es", + entryFileNames: (info) => `js/${getName(info)}.[format].js`, + chunkFileNames: (info) => `js/${getName(info)}.[format].js`, + assetFileNames: (info) => `css/${getName(info)}.[ext]`, }, }, }, @@ -95,6 +75,14 @@ const c = { server: { port: 9000, }, + resolve: { + alias: [ + { + find: "@shared", + replacement: resolve(__dirname, "../../shared"), + }, + ], + }, }; export default defineConfig(c); diff --git a/packages/unity-react-core/.storybook-configv8/dataLayerListener/withDataLayerListener.js b/packages/unity-react-core/.storybook-configv8/dataLayerListener/withDataLayerListener.js index 2e444dd022..a4f927dcac 100644 --- a/packages/unity-react-core/.storybook-configv8/dataLayerListener/withDataLayerListener.js +++ b/packages/unity-react-core/.storybook-configv8/dataLayerListener/withDataLayerListener.js @@ -10,7 +10,7 @@ export const withDataLayerListener = makeDecorator({ function removeDOMObjects(eventObject){ return Object.entries(eventObject).reduce((acc, [k, v])=>{ - acc[k] = (typeof v === "object" && v.tagName) ? v.tagName : v; + acc[k] = (typeof v === "object" && v?.tagName) ? v.tagName : v; return acc },{}) } diff --git a/packages/unity-react-core/.storybook-configv8/preset/preview.js b/packages/unity-react-core/.storybook-configv8/preset/preview.js index 66dbb9f41b..ddac55b8ac 100644 --- a/packages/unity-react-core/.storybook-configv8/preset/preview.js +++ b/packages/unity-react-core/.storybook-configv8/preset/preview.js @@ -2,10 +2,6 @@ import { withColumns } from '../withColumns'; import { customViewports } from '../viewports'; -export const globals = { - columns: false, -}; - export const initialGlobals = { columns: false, }; @@ -23,7 +19,6 @@ export const decorators = [ /** @type { import('@storybook/react').Preview } */ const preview = { - globals, initialGlobals, parameters, decorators, diff --git a/packages/unity-react-core/.storybook/decorators.tsx b/packages/unity-react-core/.storybook/decorators.tsx index 3b23852304..ef94907bd0 100644 --- a/packages/unity-react-core/.storybook/decorators.tsx +++ b/packages/unity-react-core/.storybook/decorators.tsx @@ -1,90 +1,109 @@ /** * This file houses all non-addon related decorators */ -import { renderToStaticMarkup } from "react-dom/server"; import { Decorator } from "@storybook/react"; -import React, { useLayoutEffect, StrictMode } from "react"; +import React, { forwardRef, ReactNode, StrictMode, useEffect } from "react"; import { getBootstrapHTML } from "../src/components/GaEventWrapper/useBaseSpecificFramework"; import { useChannel } from "@storybook/preview-api"; -declare global { - interface Window { - initDataLayer: () => void; - } +type ContainerComponent = React.ForwardRefExoticComponent>; + +declare interface ContainerProps { + children?: ReactNode; + dangerouslySetInnerHTML?: { + __html: string; + }; } -const Full = ({ children, rootRef }) => ( -
- {children} -
-); +const Full:ContainerComponent = forwardRef((props, ref) => { + return ( +
+)}); -const UdsContainer = ({ children, rootRef }) => ( +const UdsContainer:ContainerComponent = forwardRef((props, ref) => { + return (
-
- {children} -
+
-); - -const StaticStory = ({ args, Container, children, rootRef }) => { - useLayoutEffect(() => { - /** - * Storybook only useId() will prefix the id with this identifier allowing - * us to identify when the output is meant for bootstrap (non react) - */ - const code = getBootstrapHTML(children); - rootRef.current.innerHTML = code; - - window.initDataLayer(); - }, [args]); - return ; -}; +)}); export const withContainer: Decorator = ( StoryFn, { args, - globals: { framework = "react" }, + globals, parameters: { layout = "fullscreen" }, + viewMode, } ) => { - const root = React.useRef(null); + let { framework = "react" } = globals; + // Doc page will only render react framework + if(viewMode === "docs") { + framework = "react"; + } + const isBootstrap = framework === "bootstrap"; + const root = React.useRef(null as any); const emit = useChannel({ "HTML/CodeUpdated": () => {} }); + const mount = () => { + if (root.current) { + if (isBootstrap) { + // custom events created by eventSpy.js to allow storybook to dispatch load events after the page is loaded + document.dispatchEvent(new Event("sb_DOMContentLoaded")); + window.dispatchEvent(new Event('sb_load')); + } + + emit("HTML/CodeUpdated", { code: root.current.innerHTML }); + } + } + + const unmount = () => { + // console.log("sb unmounting"); + if (isBootstrap) { + // bootstrap script has functionality initiated with window load event, + // so we need to reload the page every time we unmount the story. + // We set the opacity to 0 to avoid flickering. + document.body.style.opacity = "0"; + + // @ts-ignore + window.unloading = true; // variable to prevent calling mount function before the page is reloaded + + // adding this timeout allows controls time to update + setTimeout(()=>{window.location.reload()}, 100); + } + } + + useEffect(() => { + // @ts-ignore + if (!window.unloading) { + mount() + } + return unmount + }, [StoryFn, args, framework]); + let Container = Full; if (layout === "container") { Container = UdsContainer; } - let html = ""; - let WrappedStory: React.ReactNode; - if (framework === "bootstrap") { - html = getBootstrapHTML(); - WrappedStory = ( - - - - - - ); - } else { - html = renderToStaticMarkup(); - WrappedStory = ( - - - + + return ( + + { + isBootstrap + ? )}}/> + : + - - ); - } - // emit the html So Addon Panel can update - emit("HTML/CodeUpdated", { code: html }); - return WrappedStory; + } + + ); }; // ordered from innermost to outermost, be careful with the order! -export const globalDecorators = [withContainer]; +export const globalDecorators = [ + withContainer +]; diff --git a/packages/unity-react-core/.storybook/eventSpy.js b/packages/unity-react-core/.storybook/eventSpy.js new file mode 100644 index 0000000000..1c85cfa49f --- /dev/null +++ b/packages/unity-react-core/.storybook/eventSpy.js @@ -0,0 +1,13 @@ +var originalAddEventListener = EventTarget.prototype.addEventListener; +EventTarget.prototype.addEventListener = function (type, listener, options) { + if(listener.uidEvent && (type === "load" || type === "DOMContentLoaded")) { + /** + * Used by storybook windowLoadEvent decorator + * creates custom events sb_load and sb_DOMContentLoaded + * to allow storybook to dispatch load events after the page is loaded + * */ + originalAddEventListener.call(this, `sb_${type}`, listener, options); + } + + originalAddEventListener.call(this, type, listener, options); +}; diff --git a/packages/unity-react-core/.storybook/preview.jsx b/packages/unity-react-core/.storybook/preview.jsx index cedd5be456..673af7e0af 100644 --- a/packages/unity-react-core/.storybook/preview.jsx +++ b/packages/unity-react-core/.storybook/preview.jsx @@ -1,10 +1,11 @@ +import "./eventSpy.js"; import "@asu/unity-bootstrap-theme/src/scss/unity-bootstrap-theme.bundle.scss"; -import "bootstrap/dist/js/bootstrap.bundle.min.js"; +import * as bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js"; +globalThis.bootstrap = bootstrap; + +import "@asu/unity-bootstrap-theme/src/js/unity-bootstrap.js"; import { globalDecorators } from "./decorators.tsx"; import { Container } from "./docPage.tsx"; -import "@asu/unity-bootstrap-theme/src/js/data-layer.js"; - -const sourceCodeRootSelector = "#html-root"; const parameters = { controls: { expanded: true }, @@ -25,13 +26,6 @@ const parameters = { }, ], }, - html: { - prettier: { - tabWidth: 4, - htmlWhitespaceSensitivity: "ignore", - }, - root: sourceCodeRootSelector, - }, docs: { // Table of contents stopped working, so I disabled it. // If we can figure out how to get it working again, we can re-enable it. diff --git a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx index 35a0214b7c..b2e9dddda6 100644 --- a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx +++ b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.jsx @@ -20,6 +20,7 @@ import { import { Button } from "../Button/Button"; import { GaEventWrapper } from "../GaEventWrapper/GaEventWrapper"; import { AnchorMenuWrapper } from "./AnchorMenu.styles"; +import { useBaseSpecificFramework } from "../GaEventWrapper/useBaseSpecificFramework"; const menuTitle = "On This Page"; @@ -43,6 +44,9 @@ export const AnchorMenu = ({ firstElementId, focusFirstFocusableElement = false, }) => { + + const { isReact, isBootstrap } = useBaseSpecificFramework(); + const anchorMenuRef = useRef(null); const isSmallDevice = useMediaQuery("(max-width: 991px)"); const [state, setState] = useState({ @@ -190,6 +194,7 @@ export const AnchorMenu = ({ // @ts-ignore requiresAltMenuSpacing={state.hasAltMenuSpacing} ref={anchorMenuRef} + id="uds-anchor-menu" className={classNames( "uds-anchor-menu", "uds-anchor-menu-expanded-lg", @@ -252,7 +257,10 @@ export const AnchorMenu = ({ ariaLabel={item.text} label={item.text} icon={item.icon} - onClick={() => handleClickLink(item.targetIdName)} + onClick={ + isReact && (() => handleClickLink(item.targetIdName)) + } + href={isBootstrap && `#${item.targetIdName}`} /> ))} diff --git a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx index dfc7fbb832..f7518e8e24 100644 --- a/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx +++ b/packages/unity-react-core/src/components/AnchorMenu/AnchorMenu.stories.jsx @@ -2,19 +2,21 @@ import classNames from "classnames"; import React from "react"; +import { + getLoremSentences, + titleCaseDefinition, +} from "../../../../../shared/constants/strings"; +import { Basic as Header } from "../../../../unity-bootstrap-theme/stories/organisms/global-header/global-header.templates"; import { Divider } from "../Divider/Divider"; +import { useBaseSpecificFramework } from "../GaEventWrapper/useBaseSpecificFramework"; import { AnchorMenu } from "./AnchorMenu"; -import { getLoremSentences, titleCaseDefinition } from "../../../../../shared/constants/strings"; -const titleCaseTitle = "Anchor Menus Should Always be Formatted with Title Case"; +const titleCaseTitle = + "Anchor Menus Should Always be Formatted with Title Case"; export default { title: "Components/AnchorMenu", component: AnchorMenu, excludeStories: ["Containers"], - globals: { - framework: "react", - }, - tags: ["!bootstrap"], parameters: { docs: { description: { @@ -54,46 +56,66 @@ const items = [ export const Containers = () => { return ( <> - {items && items.map((item, index) => { - const lastStyle = (index === items.length - 1) ? {marginBottom: "100vh"} : undefined; - return ( -
-

{item.text}

- {/* use titleCaseDefinition for the first item */} - { (index === 0) && - <> -

{titleCaseTitle}

+ {items && + items.map((item, index) => { + const lastStyle = + index === items.length - 1 ? { marginBottom: "100vh" } : undefined; + return ( +
+

{item.text}

+ {/* use titleCaseDefinition for the first item */} + {index === 0 && ( + <> +

{titleCaseTitle}

+

+ Definition: + {titleCaseDefinition} +

+ + + )}

- Definition: { titleCaseDefinition } + { + getLoremSentences( + 40, + index * 3 + ) /* 40 sentences, index * 3 offset just creates some variety */ + }

- - - } -

- {getLoremSentences(40, index * 3) /* 40 sentences, index * 3 offset just creates some variety */} -

-
- ); - })} +
+ ); + })} ); }; -const Template = args => ( -
-
-
- {/* Component */} - - {/* Demostration purposes containers */} - +const Template = args => { + const { isBootstrap } = useBaseSpecificFramework(); + + return ( + <> + {/* Bootstrap version of anchor menu depends on the header component */} + {isBootstrap &&
} + +
+
+
+ {/* Component */} + + {/* Demostration purposes containers */} + +
+
-
-
-); + + ); +}; export const Default = Template.bind({}); Default.args = { diff --git a/packages/unity-react-core/src/components/Article/Article.jsx b/packages/unity-react-core/src/components/Article/Article.jsx index 48fc2bf8eb..ebfd0c07ea 100644 --- a/packages/unity-react-core/src/components/Article/Article.jsx +++ b/packages/unity-react-core/src/components/Article/Article.jsx @@ -27,7 +27,7 @@ import { Wrapper, EventInfoWrapper } from "./Article.styles"; * @returns {JSX.Element} */ export const Article = ({ - type, + type = "news", articleUrl, publicationDate, title, @@ -192,7 +192,11 @@ export const Article = ({ data-testid="uds-hero" className="uds-hero uds-hero-md" style={{ - backgroundImage: `linear-gradient(180deg, #19191900 0%, #191919c9 100%), url(${headerImageUrl})`, + // @ts-ignore + "--color1": "#19191900", + "--color2": "#191919c9", + // moved colors to variable because hex color in linear-gradient breaks react + "backgroundImage": `linear-gradient(180deg, var(--color1) 0%, var(--color2) 100%), url(${headerImageUrl})`, }} /> )} @@ -369,17 +373,3 @@ Article.propTypes = { */ zoomUrl: PropTypes.string, }; - -Article.defaultProps = { - type: "news", - authorEmail: undefined, - authorPhone: undefined, - authorTitle: undefined, - breadcrumbs: undefined, - calendarUrl: undefined, - headerImageUrl: undefined, - eventLocation: undefined, - eventTime: undefined, - registrationUrl: undefined, - zoomUrl: undefined, -}; diff --git a/packages/unity-react-core/src/components/Button/Button.jsx b/packages/unity-react-core/src/components/Button/Button.jsx index 2f80471f9b..ad044ff7dd 100644 --- a/packages/unity-react-core/src/components/Button/Button.jsx +++ b/packages/unity-react-core/src/components/Button/Button.jsx @@ -24,21 +24,21 @@ const gaDefaultObject = { * @returns {JSX.Element} */ export const Button = ({ - label, - cardTitle, + label = "", + cardTitle = "", gaData, ariaLabel, block, - color, + color = "gray", disabled, - element, + element = "button", href, icon, innerRef, onClick, - size, + size = "default", classes, - target, + target = "_self", ...props }) => { const btnClasses = classNames("btn", { @@ -164,20 +164,3 @@ Button.propTypes = { */ target: PropTypes.oneOf(["_blank", "_self", "_top", "_parent"]), }; - -Button.defaultProps = { - label: "", - cardTitle: "", - ariaLabel: undefined, - block: undefined, - color: "gray", - disabled: undefined, - element: "button", - href: undefined, - icon: undefined, - innerRef: undefined, - onClick: undefined, - size: "default", - classes: undefined, - target: "_self", -}; diff --git a/packages/unity-react-core/src/components/ButtonIconOnly/ButtonIconOnly.jsx b/packages/unity-react-core/src/components/ButtonIconOnly/ButtonIconOnly.jsx index fe6829256c..c5fb46f526 100644 --- a/packages/unity-react-core/src/components/ButtonIconOnly/ButtonIconOnly.jsx +++ b/packages/unity-react-core/src/components/ButtonIconOnly/ButtonIconOnly.jsx @@ -22,12 +22,12 @@ const gaDefaultObject = { * @returns {JSX.Element} */ export const ButtonIconOnly = ({ - color, - icon, - innerRef, - onClick, - size, - cardTitle, + color = "gray", + icon = undefined, + innerRef = undefined, + onClick = undefined, + size = "small", + cardTitle = "", className, gaData, ...rest @@ -99,12 +99,3 @@ ButtonIconOnly.propTypes = { size: PropTypes.oneOf(["large", "small"]), className: PropTypes.string, }; - -ButtonIconOnly.defaultProps = { - color: "gray", - icon: undefined, - innerRef: undefined, - onClick: undefined, - size: "small", - cardTitle: "", -}; diff --git a/packages/unity-react-core/src/components/ButtonTag/ButtonTag.jsx b/packages/unity-react-core/src/components/ButtonTag/ButtonTag.jsx index e456cb7ef3..c60e0c1678 100644 --- a/packages/unity-react-core/src/components/ButtonTag/ButtonTag.jsx +++ b/packages/unity-react-core/src/components/ButtonTag/ButtonTag.jsx @@ -24,11 +24,11 @@ const gaDefaultObject = { * @returns {JSX.Element} */ export const ButtonTag = ({ - label, - cardTitle, + label = "", + cardTitle = "", gaData, ariaLabel, - color, + color = "gray", disabled, element = "button", innerRef, @@ -137,15 +137,3 @@ ButtonTag.propTypes = { */ onClick: PropTypes.func, }; - -ButtonTag.defaultProps = { - label: "", - cardTitle: "", - ariaLabel: undefined, - color: "gray", - disabled: undefined, - element: "button", - href: undefined, - innerRef: undefined, - onClick: undefined, -}; diff --git a/packages/unity-react-core/src/components/Card/Card.jsx b/packages/unity-react-core/src/components/Card/Card.jsx index fbdb40470e..7dc9f2c84a 100644 --- a/packages/unity-react-core/src/components/Card/Card.jsx +++ b/packages/unity-react-core/src/components/Card/Card.jsx @@ -29,22 +29,22 @@ const gaDefaultObject = { * @returns {JSX.Element} */ export const Card = ({ - type, - width, - horizontal, + type = "default", + width = "100%", + horizontal = false, image, imageAltText, title, icon, body, - eventFormat, + eventFormat = "stack", eventLocation, eventTime, buttons, linkLabel, linkUrl, tags, - showBorders, + showBorders = true, cardLink, }) => { return ( @@ -154,44 +154,26 @@ Card.propTypes = { cardLink: PropTypes.string, }; -Card.defaultProps = { - type: "default", - width: "100%", - horizontal: false, - body: undefined, - eventFormat: "stack", - eventTime: undefined, - eventLocation: undefined, - icon: undefined, - image: undefined, - imageAltText: undefined, - buttons: undefined, - linkLabel: undefined, - linkUrl: undefined, - tags: undefined, - showBorders: true, -}; - /* * Sub-components defined after this */ const BaseCard = ({ - type, - width, - horizontal, - image, - imageAltText, + type = "default", + width = "100%", + horizontal = false, + image = "", + imageAltText = "", title, - icon, - body, - eventFormat, - eventLocation, - eventTime, - buttons, - linkLabel, - linkUrl, - tags, - showBorders, + icon = undefined, + body = "", + eventFormat = "stack", + eventLocation = "", + eventTime = "", + buttons = undefined, + linkLabel = undefined, + linkUrl = undefined, + tags = undefined, + showBorders = true, cardLink, }) => { const cardClass = classNames("card", "cards-components", { @@ -297,35 +279,17 @@ BaseCard.propTypes = { cardLink: PropTypes.string, }; -BaseCard.defaultProps = { - type: "default", - width: "100%", - horizontal: false, - body: "", - eventFormat: "stack", - eventTime: "", - eventLocation: "", - icon: undefined, - image: "", - imageAltText: "", - buttons: undefined, - linkLabel: undefined, - linkUrl: undefined, - tags: undefined, - showBorders: true, -}; - const CardContent = ({ - type, - body, - eventFormat, - eventLocation, - eventTime, + type = "default", + body = "", + eventFormat = "stack", + eventLocation = "", + eventTime = "", title, - buttons, - linkLabel, - linkUrl, - tags, + buttons = undefined, + linkLabel = undefined, + linkUrl = undefined, + tags = undefined, cardLink, }) => ( <> @@ -438,19 +402,11 @@ CardContent.propTypes = { cardLink: PropTypes.string, }; -CardContent.defaultProps = { - type: "default", - body: "", - eventFormat: "stack", - eventLocation: "", - eventTime: "", - buttons: undefined, - linkLabel: undefined, - linkUrl: undefined, - tags: undefined, -}; - -const EventInfo = ({ eventFormat, eventTime, eventLocation }) => { +const EventInfo = ({ + eventFormat = "stack", + eventLocation = "", + eventTime = "", +}) => { if (eventFormat === "inline") { return (
@@ -516,9 +472,3 @@ EventInfo.propTypes = { eventLocation: PropTypes.string, eventTime: PropTypes.string, }; - -EventInfo.defaultProps = { - eventFormat: "stack", - eventLocation: "", - eventTime: "", -}; diff --git a/packages/unity-react-core/src/components/Modal/Modal.stories.tsx b/packages/unity-react-core/src/components/Modal/Modal.stories.tsx index f321fd6309..ce72416743 100644 --- a/packages/unity-react-core/src/components/Modal/Modal.stories.tsx +++ b/packages/unity-react-core/src/components/Modal/Modal.stories.tsx @@ -4,10 +4,9 @@ import { Modal } from "./Modal"; export default { title: "Components/Modal", component: Modal, - globals: { - framework: "react", + args: { + open: false, }, - tags: ["!bootstrap"], }; const modalTemplate = args => ; diff --git a/packages/unity-react-core/src/components/Modal/Modal.tsx b/packages/unity-react-core/src/components/Modal/Modal.tsx index dd0d7f95d9..cf043a551d 100644 --- a/packages/unity-react-core/src/components/Modal/Modal.tsx +++ b/packages/unity-react-core/src/components/Modal/Modal.tsx @@ -2,6 +2,8 @@ import React, { useEffect } from "react"; import { ButtonIconOnly } from "../ButtonIconOnly/ButtonIconOnly"; import { GaEventWrapper } from "../GaEventWrapper/GaEventWrapper"; +import { useBaseSpecificFramework } from "../GaEventWrapper/useBaseSpecificFramework"; +import classNames from "classnames"; /** * * TODO: Should we be using bootstrap's built in modal functionality? @@ -18,6 +20,7 @@ const defaultGaData = { }; export interface ModalProps { + open?: boolean; gaData?: { name: string; event: string; @@ -29,51 +32,59 @@ export interface ModalProps { }; } -export const Modal: React.FC = ({ gaData }) => { - useEffect(() => { - document - ?.getElementById("openModalButton") - .addEventListener("click", function () { - document.getElementById("uds-modal").classList.add("open"); - }); +export const Modal: React.FC = ({ open, gaData }) => { + const { isReact, isBootstrap } = useBaseSpecificFramework(); + const [openState, setOpen] = React.useState(open); - document - ?.getElementById("closeModalButton") - .addEventListener("click", function () { - document.getElementById("uds-modal").classList.remove("open"); - }); - }); + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; return (
-
-
- - - -

Content

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod incididuntåç ut labore et dolore magna aliqua eiusmod tempo. -

- + {(openState || isBootstrap) && ( +
+
+ + + +

Content

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod incididuntåç ut labore et dolore magna aliqua eiusmod + tempo. +

+ +
-
+ )}
); }; diff --git a/packages/unity-react-core/src/components/Pagination/PageItem/PageItem.jsx b/packages/unity-react-core/src/components/Pagination/PageItem/PageItem.jsx index 026b248efb..918b3948c5 100644 --- a/packages/unity-react-core/src/components/Pagination/PageItem/PageItem.jsx +++ b/packages/unity-react-core/src/components/Pagination/PageItem/PageItem.jsx @@ -14,11 +14,11 @@ import React from "react"; */ export const PageItem = ({ dataId, - isClickeable, - disabled, - pageLinkIcon, - selectedPage, - onClick, + isClickeable = false, + disabled = false, + pageLinkIcon = false, + selectedPage = false, + onClick = () => {}, ellipses, ariaLabel, children, @@ -69,11 +69,3 @@ PageItem.propTypes = { ariaLabel: PropTypes.string, ariaDisabled: PropTypes.bool, }; - -PageItem.defaultProps = { - isClickeable: false, - disabled: false, - pageLinkIcon: false, - selectedPage: false, - onClick: () => {}, -}; diff --git a/packages/unity-react-core/src/components/Pagination/Pagination.jsx b/packages/unity-react-core/src/components/Pagination/Pagination.jsx index 8574f174fa..1412c7a5e1 100644 --- a/packages/unity-react-core/src/components/Pagination/Pagination.jsx +++ b/packages/unity-react-core/src/components/Pagination/Pagination.jsx @@ -33,8 +33,8 @@ const defaultGAEvent = { export const Pagination = ({ type, background, - currentPage, - totalPages, + currentPage = 1, + totalPages = 10, onChange, }) => { const [selectedPage, setSelectedPage] = useState(null); @@ -230,8 +230,3 @@ Pagination.propTypes = { */ onChange: PropTypes.func.isRequired, }; - -Pagination.defaultProps = { - currentPage: 1, - totalPages: 10, -}; diff --git a/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx b/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx index c28dd57118..ffa010c883 100644 --- a/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx +++ b/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx @@ -10,6 +10,7 @@ import PropTypes from "prop-types"; import React, { useState, useEffect, useRef, useCallback } from "react"; +import { throttle, debounce } from "../../../../../shared"; import { useBaseSpecificFramework } from "../GaEventWrapper/useBaseSpecificFramework"; import { NavControls, TabHeader } from "./components"; @@ -26,19 +27,23 @@ function useRefs() { return [refs, register]; } -const Tab = ({ id, bgColor, selected, children }) => - selected && ( -
- {children} -
+const Tab = ({ id, bgColor, selected, children }) => { + const { isBootstrap } = useBaseSpecificFramework(); + return ( + (selected || isBootstrap) && ( +
+ {children} +
+ ) ); +}; Tab.propTypes = { id: PropTypes.string.isRequired, @@ -54,13 +59,15 @@ const TabbedPanels = ({ onTabChange = _ => {}, }) => { const childrenArray = React.Children.toArray(children); + if (childrenArray.length === 0) { + return null; + } const isMounted = useRef(false); const [activeTabID, setActiveTabID] = useState( initialTab && initialTab !== "null" ? initialTab : childrenArray[0].props.id ); const headerTabs = useRef(null); const [headerTabItems, setHeaderTabItems] = useRefs(); - const { isReact, isBootstrap } = useBaseSpecificFramework(); const updateActiveTabID = tab => { onTabChange(tab); @@ -72,31 +79,47 @@ const TabbedPanels = ({ const [scrollLeft, setScrollLeft] = useState(0); const [scrollableWidth, setScrollableWidth] = useState(); + const handleResize = () => { + setScrollableWidth( + headerTabs.current?.scrollWidth - headerTabs.current?.offsetWidth + ); + }; + + const handleScroll = () => { + setScrollLeft(headerTabs.current?.scrollLeft); + }; + + const throttleScroll = () => { + const timeout = 150; + // prevent function from being called excessively + throttle(handleScroll, timeout); + // ensure function executes after scrolling stops + debounce(handleScroll, timeout); + }; + + const throttleResize = () => { + const timeout = 150; + // prevent function from being called excessively + throttle(handleResize, timeout); + // ensure function executes after scrolling stops + debounce(handleResize, timeout); + }; + useEffect(() => { - const onScroll = () => { - setScrollLeft(headerTabs.current.scrollLeft); - }; - headerTabs.current.addEventListener("scroll", onScroll); - onScroll(); + headerTabs.current.addEventListener("scroll", throttleScroll); + handleScroll(); return () => { if (headerTabs.current) { - headerTabs.current.removeEventListener("scroll", onScroll); + headerTabs.current.removeEventListener("scroll", throttleScroll); } }; }, [scrollableWidth]); useEffect(() => { - const onResize = () => { - setScrollableWidth( - headerTabs.current.scrollWidth - headerTabs.current.offsetWidth - ); - }; - window.addEventListener("resize", onResize); - onResize(); + window.addEventListener("resize", throttleResize); + handleResize(); return () => { - if (headerTabs.current) { - window.removeEventListener("resize", onResize); - } + window.removeEventListener("resize", throttleResize); }; }, []); @@ -190,10 +213,10 @@ const TabbedPanels = ({ title={child.props.title} selected={activeTabID === child.props.id} gaData={trackLinkEvent} - selectTab={isReact && switchToTab} + selectTab={switchToTab} key={child.props.id} - leftKeyPressed={isReact && (() => incrementIndex(false))} - rightKeyPressed={isReact && (() => incrementIndex())} + leftKeyPressed={() => incrementIndex(false)} + rightKeyPressed={() => incrementIndex()} icon={child.props.icon} index={index} /> @@ -205,7 +228,7 @@ const TabbedPanels = ({ hidePrev={scrollLeft <= 0} hideNext={scrollLeft >= scrollableWidth} gaData={trackArrowsEvent} - slideNav={isReact && slideNav} + slideNav={slideNav} />
{ diff --git a/packages/unity-react-core/src/components/TabbedPanels/components/NavControls.jsx b/packages/unity-react-core/src/components/TabbedPanels/components/NavControls.jsx index d18eabe0da..4eec29bbfc 100644 --- a/packages/unity-react-core/src/components/TabbedPanels/components/NavControls.jsx +++ b/packages/unity-react-core/src/components/TabbedPanels/components/NavControls.jsx @@ -3,6 +3,7 @@ import React from "react"; import { GaEventWrapper } from "../../GaEventWrapper/GaEventWrapper"; import { NavControlButtons } from "./NavControls.styles"; +import { useBaseSpecificFramework } from "../../GaEventWrapper/useBaseSpecificFramework"; /** * @typedef {Object} NavControlsProps @@ -12,14 +13,15 @@ import { NavControlButtons } from "./NavControls.styles"; * @property {() => void} slideNav */ const NavControls = ({ gaData, hidePrev, hideNext, slideNav }) => { + const { isReact, isBootstrap } = useBaseSpecificFramework(); return ( - {!hidePrev && ( + {(!hidePrev || isBootstrap) && ( )} - {!hideNext && ( + {(!hideNext || isBootstrap) && (
), ], - globals: { - framework: "react", - }, - tags: ["!bootstrap"], }; export const BasicTable: StoryObj = { diff --git a/packages/unity-react-core/src/core/models/shared-prop-types.js b/packages/unity-react-core/src/core/models/shared-prop-types.js index b7d82960f9..57ddae8d5e 100644 --- a/packages/unity-react-core/src/core/models/shared-prop-types.js +++ b/packages/unity-react-core/src/core/models/shared-prop-types.js @@ -18,7 +18,7 @@ const contentPropType = PropTypes.shape({ const accordionCardPropTypes = PropTypes.shape({ color: PropTypes.oneOf(["gold", "maroon", "gray", "dark"]), content: PropTypes.shape({ - icon: PropTypes.string, + icon: PropTypes.arrayOf(PropTypes.string), header: PropTypes.string, body: PropTypes.string, }), diff --git a/packages/unity-react-core/vite.config.js b/packages/unity-react-core/vite.config.js index 9ca5287036..ce573119c8 100644 --- a/packages/unity-react-core/vite.config.js +++ b/packages/unity-react-core/vite.config.js @@ -73,6 +73,9 @@ export default defineConfig({ }, }, ], + // optimizeDeps: { + // force: true, + // }, resolve: { alias: { "@shared": path.resolve(__dirname, "./../../shared"),