@sunrise-upc/solar-components
TypeScript icon, indicating that this package has built-in type declarations

1.0.13 • Public • Published

WebComponents

Webcomponents for Solar Design System with Lit.

Setup

Instalation

Install dependencies :

~ npm i

Start

Initiate webpack watcher:

~ npm run start:dev

attention: Make sure your node version is between 14 and 16.

Folder structure

Each webcomponent folder should contain:

  • [name].ts: defines the component.
  • [name].spec.ts: defines the tests of the component.
  • types.ts: contains necessary configuration like enum,types or interface of the component.
  • styles.scss: contains the styles of the component.
  • index.ts: exports [name].ts and types.ts.

Ideally the folder name should correspond to the [name] of the component.

src/components
└── foo-bar
    ├── index.ts
    ├── foo-bar.ts
    ├── foo-bar.spec.ts
    ├── styles.scss
    └── types.ts

Inside src folder:

  • index.ts: exports all index.ts inside each component folder.
export * from './components/foo-bar/index';
  • types.ts: exports all types.ts inside each component folder.
export * from './components/foo-bar/types';
  • styles.scss: import or add any style to use outside webcomponents (optional);
@import '@sunrise-upc/solar-styles';

Development

Use the provided src/index.html to test the results in the browser.

Lit

Each component is created using Lit library.

// foo-bar.ts

import {
  css,
  CSSResultGroup,
  html,
  LitElement,
  TemplateResult,
  unsafeCSS,
} from 'lit';
import styles from './styles.scss';
import { customElement, property } from 'lit/decorators.js';
import { FooBarTypes } from './types';

const componentName = 'foo-bar';
/**
 * foo-bar description.
 * @element foo-bar
 */
@customElement(componentName)
export class FooBar extends LitElement {
  constructor() {
    super();
  }

  /**
   * myProperty description
   * @type {string}
   */
  @property({ type: String }) myProperty: FooBarTypes = 'foo';

  static get styles(): CSSResultGroup {
    return css`
      ${unsafeCSS(styles)}
    `;
  }

  render(): TemplateResult {
    return html`
      <p id="${componentName}" myattribute="${this.myProperty}">
        <slot></slot>
      </p>
    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'foo-bar': FooBar;
  }
}
// types.ts

export const FooBarEnum = {
  FOO: 'foo',
  BAR: 'bar',
} as const;

export type FooBarTypes = typeof FooBarEnum[keyof typeof FooBarEnum];

export interface FooBarI {
  myProperty?: FooBarTypes;
}
// index.ts

export * from './foo-bar';
export * from './types';

Check documentation for more information.

Component Documentation

Ideally, each component should have some basic documentation with JSDoc, which is later exported by web-components-analyzer.

A custom-elements.json with exported documentation can be created manually by running:

~ npm run schema:dev
# or
~ npm run schema:prod

This is specially useful with StorybookJS, where the documentation is automatically integrated.

Check documentation for more information of useful JSDoc tags.

Testing

JestJS is used as testing framework for the components.

// foo-bar.spec-ts

import { LitElement } from 'lit-element';
import { FooBarEnum } from './types';

describe('foo-bar', () => {
  const FOR_BAR_TAG = 'foo-bar';
  const ELEMENT_ID = 'foo-bar';

  let fooBarElement: LitElement;

  const getShadowRoot = (tagName: string): ShadowRoot => {
    return document.body.getElementsByTagName(tagName)[0].shadowRoot;
  };

  beforeEach(() => {
    fooBarElement = window.document.createElement(FOR_BAR_TAG) as LitElement;

    fooBarElement.innerHTML = 'FooBar';

    document.body.appendChild(fooBarElement);
  });

  afterEach(() => {
    document.body.getElementsByTagName(FOR_BAR_TAG)[0].remove();
  });

  it('renders correctly', () => {
    expect(fooBarElement).toMatchInlineSnapshot(`
      <foo-bar>
        FooBar
      </foo-bar>
    `);
  });

  it('set the text', async () => {
    const TEXT = 'Text';

    fooBarElement.innerHTML = 'Text';

    await fooBarElement.updateComplete;

    const text: string = getShadowRoot(FOR_BAR_TAG)
      .querySelector('slot')
      .assignedNodes()[0].nodeValue;

    expect(text).toBe(TEXT);
  });

  it('set the myattribute', async () => {
    const MY_PROPERTY = FooBarEnum.FOO;

    fooBarElement.setAttribute('myattribute', MY_PROPERTY);

    await fooBarElement.updateComplete;

    const attributeNames: string[] = getShadowRoot(FOR_BAR_TAG)
      .getElementById(ELEMENT_ID)
      .getAttributeNames();

    expect(attributeNames).toContain('myattribute');
  });
});

Use the following commands to run the tests:

~ npm run test:dev
# or
~ npm run test:prod

Use the following command to develop tests:

~ npm run test:watch

Use the following command to update snapshots testing:

~ npm run test:update-snapshot

Check documentation for more information.

Builder

Webpack is the bundler responsible for building the library.

  • webpack.common.js: contains rules for development and production mode.
  • webpack.dev.js: contains rules specific for development mode.
  • webpack.prod.js: contains rules specific for production mode.

Some of the tasks of the bundler are:

  • compiling .ts, .scss.
  • handling fonts and svg.

When creating new webcomponents add an entry with the corresponding index.ts in the entry: {...} section of webpack.common.js file:

module.exports = {
  entry: {
    index: './src/index.ts',
    types: './src/types.ts',
    'components/foo-bar': './src/components/foo-bar/index.ts',
  },
  (...)
}

This will make possible to import each component individually.

Use the following commands to run builder:

~ npm run build:dev
# or
~ npm run build:prod

Check documentation for more information.

Commands

Others available commands are:

  • linter: runs the linter.
~ npm run lint:js
# or
~ npm run lint:css
  • fixer: runs linter and fixs possible issues.
~ npm run lint:js:fix
# or
~ npm run lint:css:fix

Additionally you can lint both .js,.ts and .css,.scss with:

~ npm run lint:all
# or
~ npm run lint:all:fix

For more information about other commands, check the scripts section in package.json file.

Git Committs

When committing any changes, linter runs to make sure code is formatted correctly, via husky and lint-staged. If it throws an error, check the output and make sure to fix the issues.

Distribution

Release

Distribution is done with bitbucket pipelines.

When merging into master branch, the following processes occurr:

  • Installation of dependencies
  • Linting of files
  • Building of compiled files with build:prod
  • Testing of compiled files test:prod
  • Release of package in npm.js

When pushing into the repository, the first three processes occur.

When creating a pull-request, the first four processes occurr.

It's possible to make a release without bickbucket pipelines by running:

~ npm run release:manual

For more information of all processes check bitbucket-pipelines.yml file.

attention: Make sure to keep all unwanted files from distribution listed on .npmignore file.

attention: When Creating a pull-request, pipelines will trigger two times:

  • branch pipe
  • pull-request pipe

This is a limitation, so the branch pipe can be stopped manually to avoid two pipes running at same time.

Cache

In order to increase the speed of bitbucket pipelines, a cache for node is setup. In case of installating new packages it's good to clean it beforehand, otherwise the install process will not contains the new packages, and the process might fail.

Versioning

It's automaticaly done by standard-version.

run 'npm run commit_cz' to help building the commit messages and define a standard way of committing rules. Commitizen

After updating package.json inside the pipelines with the [new-version-number], a new commit is created in master branch:

chore(release): 0.0.[new-version-number]

Publishing

It's done with Bitbucket's deploy-to-npm pipe.

Because it's pushing back to the repository it always creates an empty commit to trigger the publish.

After each publish, a new commit is created in master branch:

[skip ci] Trigger from pipelines release step.

attention: Make sure always pull master after each release so the two new commits are available locally.

Usage

Package

Install as a package:

~ npm install @sunrise-upc/solar-components --save

Implementation

Use the whole library:

import '@sunrise-upc/solar-components';
// Import any necessary configuration to use with components.
import { fooBarEnum } from '@sunrise-upc/solar-components';

<foo-bar></foo-bar>;

Or use each component individually:

import '@sunrise-upc/solar-components/dist/components/foo-bar';
// Import any necessary configuration of this component.
import { fooBarEnum } from '@sunrise-upc/solar-components/dist/components/foo-bar';

<foo-bar></foo-bar>;

attention: currently is no possible to import whole library together with individual components, as this creates duplication of elements registration. Make sure to decide between whole library/individual components.

Integration

React

To use the webcomponents in a React its necessary to declare the corresponding IntrinsicElements interface:

// declaration.d.ts

import { HTMLAttributes } from 'react';
import { PrimaryButton, ButtonI } from '@sunriseupc/solar-components';

declare global {
    namespace JSX {
        interface IntrinsicElements {
            'primary-button': PrimaryButtonAttrs;
        }
        interface PrimaryButtonAttrs extends HTMLAttributes<HTMLElement>, ButtonI {
            ref?: React.RefObject<PrimaryButton>;
        }
    }
}
// Entry point file

import '../styles/globals.css'
import type { AppProps } from 'next/app'
import React, { useEffect } from 'react';

function MyApp({ Component, pageProps }: AppProps) {
  React.useEffect(()=> {
    (async ()=> await import('@sunriseupc/solar-components'))();
  });

  return <Component {...pageProps} />
}

export default MyApp

Make sure that declaration.d.ts is integrated in tsconfig.json.

This assures that:

  • children prop is accessible.
  • onClick or other event handlers can be added.
  • a ref can be created on the component.
import React, { FC, FormEvent, useEffect, useRef, useState } from 'react';
import '@sunrise-upc/solar-components/dist/components/buttons/primary-button';
import { PrimaryButton } from '@sunrise-upc/solar-components/dist/components/PrimaryButton';

interface Props {}

const ReactComponent: FC<Props> = (): JSX.Element => {
  const refPrimaryButton = (useRef < PrimaryButton) | (null > null);

  const handleClickListerner = (event?: Event): void => {
    console.log(event);
  };

  useEffect(() => {
    refPrimaryButton.current?.addEventListener('click', handleClickListerner);

    return (): void => {
      refPrimaryButton.current?.removeEventListener('click', handleClickListerner);
    };
  });

  return (
    <section>
      <primary-button ref={refPrimaryButton} myAttribute="foo"></primary-button>
      <primary-button onClick={(e): void => console.log(e)}></primary-button>
    </section>
  );
};

export default ReactComponent;

Readme

Keywords

none

Package Sidebar

Install

npm i @sunrise-upc/solar-components

Weekly Downloads

1

Version

1.0.13

License

ISC

Unpacked Size

24.1 MB

Total Files

495

Last publish

Collaborators

  • filipecruz
  • marcorusso
  • caroline.santos
  • ramalingamh
  • subhashish.samant
  • mysoreshivakumar
  • sunrise-b2c
  • sunrise-ecomm
  • alexcibotari
  • hirts
  • lamv
  • rui_fonseca