0.1.93 • Published 9 months ago

@lumavate/core v0.1.93

Weekly downloads
58
License
ISC
Repository
github
Last release
9 months ago

Lumavate Core

This package includes helpers for developing components to be used within the Lumavate platform.

Currently, this package only helps with the post build, storybook development, and a couple of helpful modules. Overtime this will hopefully grow into a more robust core library that will be the base for all future Lumavate component development.

This library is dependent on Stencil, and Storybook. This documentation is only to guide with setup of @lumavate/core. For guidance on writing components and stories, please refer to the above links.

Intro

Why is this needed? Lumavate currently has many component libraries, all using their own copy/pasted post-build scripts and styleData. This was a great first step, but over time these have gotten out of sync as changes and improvements have been made. During development of these libraries, we also discovered Storybook, an extremely helpful library developed specifically to test components during development; rather than commenting/uncommenting components in a main index.html file from stencil dev-server. This was a great upgrade, but components being tested then were not accurately representing how they would be laid down in Page Builder v2. So some helpers are included for storybook development to replicate how they would be laid down in Page Build v2.

Table Of Contents

Getting Started

Step One: Install Dependencies

First install Storybook for HTML and its dependency, babel, if you haven't already. These are peer dependencies of @lumavate/core.

cd my-component-library
Add @storybook/html as a devDependency
npm install @storybook/html --save-dev
Add @babel/core and babel-loader as a devDependency
npm install babel-loader @babel/core --save-dev
Add @lumavate/core as a dependency
npm install @lumavate/core --save

This will also copy to the root of your project two folders

-my-project
 .luma <- Lumavate build tools
	luma-loader.tpl.js <- helper loader for Platform direct includes script
	style-data.json    <- Contains the new default theme color layout to be used on all component libraries (until its moved to the app level).
	post-build.sh      <- This runs post build to compile individual component.json files along with style-data.json into dist/components.json. No components.json file is needed at the root.
	                      However, if your library is using a main components.json at the root of the project, then this single file will be copied instead, and style-data.json will be ignored.
 .luma-storybook <- Lumavate storybook helper for platform Page Builder testing of components
    *contains many files and a custom decorator to lay components out
     like they would be defined in Page Builder

Step Two: Add Scripts

Then add/replace the following NPM script to your package.json in order to run the storybook as well as correctly utilize the new build process. The dependencies used here are included with the @lumavate/core package except for storybook...for now.

{
"scripts": {
    "lint": "jslint '**/*.component.json'",
    "build": "npm run lint && stencil build --docs",
    "dev-build": "npm run lint && stencil build --dev && npm run postbuild",
    "postbuild": ".luma-build/post-build.sh",
    "storybook": "start-storybook --ci -s ./src/assets,www -p 9000 -c .luma-storybook",
    "storybook-watch": "run-p start storybook",
    ...
}

Step Three: Update .gitignore

Add the newly added .luma-storybook/ and .luma-build/folders to .gitignore. These are tied to the @lumavate/core module and will be removed if the package is ever uninstalled. These files should never have to be modified from a repository using the package.

  ...
  .stencil/
  node_modules/
  .luma-build/
  .luma-storybook/
  ...

Local Installation

For creating a local version of @lumavate/core to test, update, and create new files

Step 1

First go to the lumavate-core repository in Github (https://github.com/Lumavate-Team/lumavate-core) and clone the repository into a folder above your project.

Step 2

Navigate to the folder in terminal. Find the branch most ahead of master and create a feature branch off of this branch with the name of whatever file you are editing. Ex: layout-adjustments.ts file -> git checkout -b feauture/layout-adjustments

Step 3

In this branch, make your changes to @lumavate/core. For ts files add them to the src folder, and make sure they are being exported with the index.ts file

Step 4

After completing your changes, go into the package.json file and incriment the version number to one higher than before in the line 3 of the file where it says "version". Ex: 0.0.34 -> 0.0.35

Step 5

Go back to your terminal and run "npm run build" in your terminal window. You should see a result similar to "> @lumavate/core@0.0.36 build > tsc && tsc -p tsconfig-cjs.json"

Step 6

Then run "npm pack". It should generate a response that shows a .tgz file was created and you should see your version number in the message it returns.

Step 7

Navigate back to your project folder and make sure you have @lumavate/core installed. If you do not, follow the steps in Getting Started. Once you have ensured you have @lumavate/core in your project, run "npm uninstall @lumavate/core --save" in your terminal window.

Step 8

Delete the .luma-build and .luma-storybook folders from your project files at the top level.

Step 9

Run the following command "npm install file path to your lumavate core folder/lumavate-core/lumavate-core-your version number.tgz --save". Ex: "npm install /Users/lumavate/code/components/lumavate-core/lumavate-core-0.0.35.tgz --save".

Step 10

If this command succeeds, then check your package-json folder to ensure that @lumavate/core is included in the dependencies section. Should be something in the format of ""@lumavate/core": "file:../lumavate-core/lumavate-core-0.0.35.tgz". If this is present then test your desired import from @lumavate/core using the format 'import { your object or function name } from "@lumavate/core"'. If this does not produce an error message, you have finished.

Modular Component Design

While the older components.json file is still supported with this new build process, it is being deprecated in favor of a modular component design, allowing for multiple people to work in the same repos at the same time, and for readability/maintenance.

Modular Component Development: 1. Each component can have its own <component>.component.json file. Notice it is singular, and not components.json. You can also group components by folder into a single component.json file

Example

Lets say we have a card.tsx component. This component can handle rendering 3 different variations of cards, each exposing properties unique to that rendering type. Two valid ways of specifiying these component.json files

Main Component file

  • card.tsx

Individual component json files for three separate platform defined components

{
	"components":[
	{
		...
		"type":"luma-ion-standard-card",
		"template":"...",
		"properties":[]
	}]
}

standard-card.component.json

{
	"components":[
	{
		...
		"type":"luma-ion-image-card",
		"template":"...",
		"properties":[]
	}]
}

image-card.component.json

{
	"components":[
	{
		...
		"type":"luma-ion-expanded-card",
		"template":"...",
		"properties":[]
	}]
}

expanded-content-card.component.json

or

Combined component.json file

{
	"components":[
	{
		"type":"luma-ion-standard-card",
		"template":"...",
		"properties":[]
	},
	{
		"type":"luma-ion-image-card",
		"template":"...",
		"properties":[]
	},
	{
		"type":"luma-ion-expanded-card",
		"template":"...",
		"properties":[]
	}]
}

card.component.json

Using JSON Arrays

Sometimes it is useful to use arrays of properties instead of individually defining each attribute multiple times, for example when using a slide component where each slide has its own properties. Soon, the additional properties wont have to be defined for each index in the array, but the data will still need to be.

The component.json template property utilizes jinja2 rendering capability. This means we can use loops. Be sure to escape the quotes on attributes defined in a json array or the json won't be considered valid. Then repeat the properties, grouping them by index.

{
  "template":[
    "<luma-ion-slides",
    "standard-attribute='{{componentData.attribute}}'",
    "data='[{% for n in range(6) %}",
    "{\"image\":\"{{componentData['image' ~ n].preview}}\",",
    "\"alt\":\"{{componentData['imageAlt'~n]|e}}\",",
    "\"link\":\"{{componentData['link'~n].url}}\" }",
    "{% if n<5 %},",
    "{% endif %}{% endfor %}]'></luma-ion-slides>"
  ],
  "properties":[
          {
            "name": "image0",
            "label": "Image",
            "type": "image-upload",
            "classification": null,
            "section": "Slide 1",
            "helpText": "Image for the slide.",
            "options":{}
          },
          {
            "name": "imageAlt0",
            "label": "Alt Text",
            "type": "translated-text",
            "classification": null,
            "section": "Slide 1",
            "helpText": "Alt Text used by screen readers for image.",
            "default": {
              "en-us": ""
            },
            "options":{}
          },
          {
            "name": "link0",
            "type": "page-link",
            "label": "Link",
            "classification": null,
            "section": "Slide 1",
            "helpText": "An optional page link can be set to make the slide clickable.",
            "default": null
          },
  ]
}

When the template is actually rendered in the platform, the line breaks will be removed and the data can be parsed using the helper modules as mentioned in Using @lumavate/core module Helpers

Build Process

After the components are built, the .luma-build/post-build.sh script file will execute.

Post Build Steps

Step 1: To be backwards compatible with older component development utilizing a single monolithic components.json file, this file will be copied to dist/ if it exists. However, if this is new development, or the library can easily be switched, then remove this file and break it up into individual .component.json files(singular) as described above in Modular Component Design

These individual component.json files will then all be merged together, to a temp file, and then that temp file is then combined with the style-data.json file located in .luma-build/. This single components.json file is then placed in the dist/ folder.

Step 2: Assets are copied from src/assets to dist/assets

Step 3: Two files are created in the dist/ folder for use in directIncludes and directCssIncludes based on the namespace of the library as defined in stencil.config.ts

  • namespace.js
  • namespace.css

NOTE: Include them directly as is, do not include a namespace/ folder in front when specifying in directIncludes and directCssIncludes.

Step 4: Finally, everything is zipped to dist/Archive.zip

Storybook Development

Storybook is an incredibly helpful tool for testing components with different permutations of properties, and also for debugging pesky layout issues we've all had to deal with. For detailed reference on writing stories, see Writing Stories.

A custom Lumavate Storybook wrapper is included in this package under .luma-storybook/ This config will be loaded when the scripts mentioned above are used. This wrapper will place components down exactly as the Page Builder would. The column start/span, item and grid padding, and grid max width properties are all configurable. A header and footer can also be specified.

Step 1: Similar to the component.json modular files, storybook files should be created in a similar manner. Using the card.tsx example from above, here is how stories would be laid out, using .js extension. Typescript support will be added soon.

 src/
  components/
    card/
       card.tsx
       image-card.component.json
       standard-card.component.json
       expanded-content-card.component.json
       image-card.stories.js
       standard-card.stories.js
       expanded-content-card.stories.js

Using npm run storybook-watch this will start the stencil dev server in parallel with the storybook server. and load all storybook files into the preview...

Then you can make changes to the component or story and see updates in the storybook preview.

Lumavate Story available options

After specifiying a story, you can configure it if needed. Various properties are available. None are required and all have defaults.

{
  lumavateVariables: {
    sections: {
      header: '<header goes here>',
      footer: '<footer goes here if needed'
    },
    layout: {
      columns: Total # of columns to use in grid,
      colStart: Column number to start at,
      colSpan: Number of columns to span,
      cellPadding: '12px',
      itemPadding: '4px',
      ionicLayout: true/false determines old or new ionic layout
    }
}

available story options

Example storybook file utilizing all properties available with the Lumavate storybook wrapper

import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

export default {
  title: 'Button|Solid',
  parameters: {
    viewport: {
      viewports: INITIAL_VIEWPORTS,
      defaultViewport: 'iphonex'
    }
  }
};

export const defaultButton = () => `<luma-ion-button text="Click Here"></luma-ion-button>`;

export const withSomeOptions = () => `<luma-ion-button text="Click Here" full-width></luma-ion-button>`;
withSomeOptions.story = {
  parameters: {
    lumavateVariables: {
      layout: {
        columns:3,
        colStart:1,
        colEnd:2
      }
    }
  }
}

export const withHeaderAndFooter = () => `<luma-ion-button text="Click"></luma-ion-button`;
withHeaderAndFooter.story = {
  parameters: {
    lumavateVariables: {
      sections: {
        footer: `<luma-ion-footer  data=\'[
                  {"icon":"home", "link":"/", "text":"Facebook"},
                  {"icon":"eye", "link":"https://twitter.com", "text":"Twitter"},
                  {"icon":"basketball", "link":"https://instagram.com", "text":"Instagram"},
                  {"icon":"logo-slack", "link":"https://linkedin", "text":"LinkedIn"}
                  ]\'></luma-ion-footer>`,
        header:`<luma-ion-menu-header image="./assets/YML_logo.png" id="mymenu"
	        data='[
	        {"text":"Lumavate", "link":"https://lumavate.com"},
	        {"text":"Google","link":"https://google.com"}
	        ]'>
        </luma-ion-menu-header>`
      }
    }
  }
}

example.stories.js

Using Theme Colors

Colors defined in style-data.json are loaded into Page Builder widget as dash cased css variables

primaryColor-> --primary-color

secondaryColor -> --secondary-color

To utilize these in a component, just reference them as a variable

buttonColor= 'var(--primary-color)';

or

style={{'color':'var(--primary-color)'}}

HOWEVER, if using Ionic components, then some additional mapping needs to be done to correctly use the colors inside of the ionic components. Please see Ionic Style Helper below for guidance.

Using Helpers

auth-helper.ts

This module includes the helpers we use when making calls to an api from a component. To use them, include the module import {authHelper} from '@lumavate/core'; You can then access its functions listed below instead of from window. - getCookieValue(cookieName) - getAuthUrl() - getActivationId() - getSingleUseToken(onSuccess, onNoAuth, onError) - checkToken()

ionic-helper.ts

This module is specific to ionic components and does not need to be used if you are not using ionic. However, due to the way we are overriding all default color behavior in Ionic, this module MUST be used if you are using ionic components to properly map colors. This will handle creating css variagles for each ionic color, as well as css classes for the colors shades and tints. Refer to Adding Ionic Colors for some clarity.

Basically, every color we define as a property in the platform, such as #AABBAA, gets turned into a css variable --ion-color-aabbaa

These then can be used as either css variables or names(when used as a stencil attribute instead of a css style)

To use this, first import it

import {StyleHelper} from '@lumavate/core';

It then needs to be instantiated in your component to correctly add css variables scoped to the component.

export class Card{
   styleHelper: StyleHelper
   @Element() hostEl: HTMLElement;

   componentWillLoad(){
      this.styleHelper = new StyleHelper(this.hostEl);
      // Now add ALL colors used in the component to the style helper so that they can be defined for ionic properly
      let colors = [];
      colors.push(this.card.header.color);
	  colors.push(this.card.subheader.color);
	  colors.push(this.card.content.color);
	  colors.push(this.card.backgroundColor);
      this.styleHelper.setIonicColors(colors);

   }
}

There are also two helper color functions useful with Ionic. import {colorName, colorVariable } from '@lumavate/core';

colorName(color:string) returns the ionic color name for a given color variable

var(--primary-color) => primary

colorVariable(color, variant=undefined) returns a css variable for a given Ionic color name or hex color

var(--primary-color) => var(--ion-color-primary)

secondary => var(--ion-color-secondary)

When to use colorName vs colorVariable?

When assigning a color to a stencil attribute like <button color="..." /> on an ionic component, then use colorName. If assigning a color to a style or another css variable like style={{'color':... }} then use colorVariable.

Sometimes, you might have to manually define states for a color(activated/focused/hover). Due to us overriding default color behavior, we need to manually set up these css variable states too....

import {colorName} from '@lumavate/core'

render(){
   let background = colorName(this.card.backgroundColor);
   // using colorName instead of colorVariable here so that a suffix can be appended
   this.backgroundStyles = {
     '--background':`var(--ion-color-${background})`,
     '--background-activated':`var(--ion-color-${background}-shade)`,
     '--background-focused': `var(--ion-color-${background}-shade)`,
     '--background-hover':`var(--ion-color-${background}-tint)`
   };
  return (<Host styles={this.backgroundStyles}>
  ...
  );
 }
}

utils.ts

parseJsonWithLines Parses a json serialized string, escaping certain characters. Jinja2 escapes new lines differently than html, so we have to parse it twice, first with this, to correctly serialize the JSON string. Then we need to manually parse individual properties in the array using parseNewLine

import {parseJsonWithLines} from '@lumavate/core';

export class Slides{
  @Prop() data: string; // json serialized array
  @State() items: [];
  ...

  componentWillLoad(){
   this.items = parseJsonWithLines(this.data)
  }

parseNewLine Returns parsed json back to standard new lines for display. This is needed after parseJsonWithLines as that just gets the entire json string ready to be parsed. But then the new lines are encoded and need to be put back when displayed. Pay attention to mapSlideData setting of the content property.

import {parseJsonWithLines} from '@lumavate/core';

export class Slides{
  @Prop() data: string; // json serialized array
  @State() items: [];
  ...

  componentWillLoad(){
   this.items = parseJsonWithLines(this.data).map(slide=>this.mapSlideData);
  }
   mapSlideData(slide){
      // apply defaults if not provided
	  return {
	      verticalAlign: slide.vAlign || this.defaultVerticalAlign,
	      link: slide.link || '',
	      image: {
	        url: slide.image,
	        alt: slide.alt || slide.image || ''
	      },
	      header: {
	        text: slide.header || this.defaultHeaderText,
	        align: slide.headerAlign || this.defaultTextAlign,
	        color: slide.headerColor || this.defaultTextColor
	      },
	      content: {
	        text: slide.content ? parseNewline(slide.content) : '',
	        align: slide.contentAlign || this.defaultTextAlign,
	        color: slide.contentColor || this.defaultTextColor
	      }
	    }
   }

interval.ts

The Interval class manages creating and deleting intervals. It takes an intervalTime and a callback function as parameters.

createInterval creates a new setInterval instance

deleteInterval deletes the a created interval

import { Interval } from '@lumavate/core'

export class IntervalComponent {
  private interval: Interval

  componentWillLoad() {
    this.interval = new Interval(100, this.greeter)
    this.interval.createInterval()
  }

  componentDidUnload() {
    this.interval.deleteInterval()
  }

  greeter(){
    console.log('Hello!')
  }
}

createIntervalPromise returns a Promise that has interval wrapped inside of it. It passes a resolve function into the callback. This gives the abilitly to perform custom actions whenever the callback resolves the promise.

setCallback sets the callback. This is useful when wrapping Interval in another class and the callback method isn't available until the wrapper class is initialized.

Here is an example of how using createIntervalPromise works in practice. The checkHeight callback method resolves when the elements height is above zero. The component is now able to wait until the height is ready to run adjustOffsets.

import { Interval } from '@lumavate/core'
import { Callback } from './utils';

export class IntervalComponent {
  private interval: Interval
  @Element() el: HTMLElement;

  componentWillLoad() {
    this.interval = new Interval(100, this.checkHeight)
    this.interval.createIntervalPromise().then(() => {
        this.interval.deleteInterval()
        this.adjustOffsets()
      })
    })
  }

  componentDidUnload() {
    this.interval.deleteInterval()
  }

  checkHeight(resolve: Callback) {
    if (this.el.offsetHeight > 0) {
      resolve()
    }
  }

  adjustOffsets(){
    //peform actions that rely on the elements height being above 0
  }

height-checker.ts

The HeightChecker class is used for waiting until an elements height has risen above a specified threshold. It takes an element, heightThreshold, and intervalTimeout as parameters.

onHeightSet returns a promise that resolves when the elements height becomes greater than the heightThreshold.

import { HeightChecker } from '@lumavate/core'

export class MyComponent {
  private parent: HTMLDivElement
  private hc: HeightChecker

  componentDidLoad() {
    this.hc = new HeightChecker(this.parent, 0, 100)
    this.hc.onHeightSet().then(() => {
      console.log('height is greater than zero!')
    }).catch((err)=>{()
      console.error(err)
    })
  }

  componentDidUnload() {
    this.hc.deleteInterval()
  }

layout-adjustments.ts

The getDimensions function is used for resizing components based on page sizing, page padding, headers, footers, and columns. It takes in a attributeVal of the value to search for, attribute for the attribute to check for the search string, and a tagName for the elements to be searched through

import { getDimensions } from "../../global/layout-adjustments"

let dimensions = getDimensions(this.url, "url", "luma-linkedin-post");
this.postWidth = dimensions.width + "px";
this.postHeight = dimensions.height + "px";

More to come

0.1.91

10 months ago

0.1.92

9 months ago

0.1.93

9 months ago

0.1.86

10 months ago

0.1.87

10 months ago

0.1.85

11 months ago

0.1.83

12 months ago

0.1.84

11 months ago

0.1.83-beta.2

1 year ago

0.0.63

12 months ago

0.1.80

1 year ago

0.1.81

1 year ago

0.1.82

1 year ago

0.1.83-beta.1

1 year ago

0.0.62

1 year ago

0.1.75

1 year ago

0.1.76

1 year ago

0.1.77

1 year ago

0.1.78

1 year ago

0.1.79

1 year ago

0.1.64

1 year ago

0.1.65

1 year ago

0.1.66

1 year ago

0.1.67

1 year ago

0.1.68

1 year ago

0.1.63

1 year ago

0.0.60

2 years ago

0.0.61

2 years ago

0.0.59

2 years ago

0.0.57

2 years ago

0.0.58

2 years ago

0.0.56

2 years ago

0.0.55

2 years ago

0.0.53

3 years ago

0.0.54

3 years ago

0.0.52

3 years ago

0.0.51

3 years ago

0.0.50

3 years ago

0.0.49

3 years ago

0.0.48

3 years ago

0.0.46

3 years ago

0.0.47

3 years ago

0.0.45

3 years ago

0.0.40

3 years ago

0.0.41

3 years ago

0.0.42

3 years ago

0.0.43

3 years ago

0.0.44

3 years ago

0.0.39

3 years ago

0.0.37

3 years ago

0.0.36

3 years ago

0.0.34

3 years ago

0.0.33

4 years ago

0.0.32

4 years ago

0.0.31

4 years ago

0.0.30

4 years ago

0.0.25

4 years ago

0.0.26

4 years ago

0.0.27

4 years ago

0.0.28

4 years ago

0.0.29

4 years ago

0.0.24

4 years ago

0.0.20

4 years ago

0.0.21

4 years ago

0.0.10

4 years ago

0.0.12

4 years ago

0.0.13

4 years ago

0.0.14

4 years ago

0.0.15

4 years ago

0.0.16

4 years ago

0.0.8

4 years ago

0.0.17

4 years ago

0.0.19

4 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago

2.0.0

4 years ago