wp-sequoia-admin-interface

1.0.2 • Public • Published

Sequoia Custom JavaScript and Blocks - Gutenberg Editor

General Information

Resource Version
Node.js v12.16.0
Npm ^v6.0.0
React v16.13.1
Gutenberg v6.0.0

Workflow

Create an Admin Page

For the creation of an admin page, please follow the next steps:

  1. Add the route of the menu that you will be working at, to the admin.php.

    1. Use the same callback function in other menu entries.
  2. Add the configuration to the menus.ts

  3. Add the routes that you are going to need on your admin into the routes.ts

    {
      path: '/the-route-path',
      title: 'submenutitle',
      component: ReactComponent
      useStore: BooleanIndicatingIfThisUsesStoreInsteadOfState
    }
  4. Create the Components under /components/ directory.

Create a Gutenberg Block

  1. Create the following folder structure at /components/Blocks/{type-of-block}

    • The {type-of-block} can be according to what is going to do the Component or which post type will use it
    • Folder Structure:
    📁 NameOfComponent
    ├─ 📄 attributes.ts
    ├─ 📄 edit.tsx
    └─ 📄 index.ts
    
    • NameOfComponent/: The folder name should always be CamelCase formatted (first letter capitalized).
    • edit.tsx: File contains the Component that we want to show on the Editor
    • index.ts: Contains all the configuration of the Block
    • attributes.ts:
  2. Once the Component is created, add it to the BlockManager.js

Development

Building and Watching

  • npm start: Creates a development environment to compile and watch TypeScript and Sass files.
  • npm run build: Generates the final output in JavaScript and CSS format.
  • npm run build:devel: Generates the extended version of all files with the development configuration.

Formatters

These scripts use Prettier to beautify the code. Beware that Prettier only formats code, if you want to linter the code please read the Linters section.

If you want to exclude files from formatting, add the respective patterns to the .prettierignore file (it uses gitignore syntax). Read more about this here.

  • npm run ts:format: Prettifies the TypeScript code.
  • npm run js:format: Prettifies the JavaScript code (mainly workflow configuration files).
  • npm run json:format: Prettifies all JSON files.

Linters

  • npm run ts:lint & npm run js:lint: Print the linter errors for TypeScript and JavaScript files respectively.
  • npm run ts:lint:fix & npm run js:lint:fix: Fix the linter errors (only those that are automatically fixable) and print the rest.
  • npm run sass:lint: Print the linter errors for Scss files.
  • npm run sass:lint:fix: Fix the linter errors (only those that are automatically fixable) and print the rest.

Testing and Other

Naming Conventions

// TODO:

WordPress:

- Naming conventions
- ESLint Rules
- Best Practices

Justia naming conventions:

- Naming conventions
- ESLint Rules
- Best Practices

New Components

New components can be organized in two ways:

Folder (Recommended)

Use the following folder structure when the component has multiple sub-modules and styles (also in sub-modules):

 📁 NewComponent
 ├─ 📄 index.tsx
 ├─ 📄 index.test.tsx
 ├─ 📄 helper-function.ts
 ├─ 📄 index.module.scss
 └─ 📁 styles
    └─ 📄 extra-styles.scss
  • NewComponent/: The folder name should always be CamelCase formatted (first letter capitalized).
  • index.tsx:
    • Main entry file. Webpack will resolve to this file when searching for the component when imported.
    • File name must be index when nested in a folder.
    • The extension must be .tsx if the component uses React. Otherwise, use .ts.
  • index.test.tsx: Read more about tests here.
  • helper-functions.ts:
    • This represents one of many possible modular files. You can add as many as you need.
    • File name should always be kebab-case formatted.
    • The extension must be .tsx if the any of the helper functions uses React. Otherwise, use .ts.
  • index.module.scss:
    • Main styles used directly in the React component.
    • This file should always match the name of the main entry file followed by the extension .module.scss. The extension is required for Webpack to identify and properly set the CSS classes.
  • styles/:
    • Create this folder if you need to modularize the styles within index.module.scss.
    • As shown above, the files contained in this folder should be named in kebab-case format and followed only by the extension .scss.
    • You can create as many sub-modular styles as you want and import them in the main file using the @import directive.

Files

This method is recommended for short components with short styles.

📄 NewComponent.tsx
📄 NewComponent.module.scss
  • NewComponent.tsx:
    • The file name should always be CamelCase formatted (first letter capitalized).
    • The extension must be .tsx if the component uses React. Otherwise, use .ts.
  • NewComponent.module.scss:
    • Main styles used directly in the React component.
    • This file should always match the name of the main entry file followed by the extension .module.scss.

Styles

There is no need to manually add CSS vendor prefixes (-webkit-, -moz-, -ms-, -o-) since Webpack internally uses PostCSS and Autoprefixer to automatically add them based on the specified browser support in the package.json.

TypeScript

These are some of the practices that this project uses. To learn more about do's and don'ts please read this.

Don't Use any

Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript. The compiler effectively treats any as "please turn off type checking for this thing". It is similar to putting an @ts-ignore comment around every usage of the variable. This can be very helpful when you are first migrating a JavaScript project to TypeScript as you can set the type for stuff you haven't migrated yet as any, but in a full TypeScript project like this one you are disabling type checking for any parts of your program that use it.

In cases where you don't know what type you want to accept, or when you want to accept anything because you will be blindly passing it through without interacting with it, you can use unknown.

type Over interface

This is mainly for consistency.

All types should be in CamelCase format (first letter capitalized).

Object Literals / Functions
type Point = {
    x: number;
    y: number;
};

type SetPoint = (x: number, y: number) => void;
Other Types

Unlike an interface, the type alias can also be used for other types such as primitives, unions, and tuples.

// Primitive
type Name = string;

// Tuple
type Data = [number, string];

// Object Literal
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// Union
type PartialPoint = PartialPointX | PartialPointY;

// Intersection
type Points = PartialPointX & PartialPointY;
Implements

A class can implement an interface or type alias, both in the same exact way. Note however that a class and interface are considered static blueprints. Therefore, they can not implement / extend a type alias that names a union type.

/* OK */

type Point = {
    x: number;
    y: number;
};

class SomePoint implements Point {
    x = 1;
    y = 2;
}

/* WRONG */

type PartialPoint = { x: number; } | { y: number; };

class SomePartialPoint implements PartialPoint { // Can not implement a union type.
    x = 1;
    y = 2;
}
Declaration Merging

In this case you will need interfaces since merging is not supported in type aliases.

You can use the declaration merging feature of the interface for adding new properties and methods to an already declared interface. This is useful for the ambient type declarations of third party libraries. When some declarations are missing for a third party library, you can declare the interface again with the same name and add new properties and methods.

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Window {
    foo: string;
    bar(): void;
}

Since the linter disallows the usage of interfaces. You may need to add the respective disabling comment as shown above.

Read-only Function Parameters

Mutating function arguments can lead to confusing, hard to debug behavior. Whilst it's easy to implicitly remember to not modify function arguments, explicitly typing arguments as readonly provides clear contract to consumers. This contract makes it easier for a consumer to reason about if a function has side-effects.

// `Readonly` utility
type FunctionProps = Readonly{
    label: string;
    type: number;
    x: number;
    y: number;
};

// `readonly` keyword
type FunctionProps = {
    readonly label: string;
}

// Arrays
type Data = readonly [string, number];

type FunctionProps2 = Readonly{
    labels: readonly string[];
    types: ReadonlyArray<number>;
    x: number;
    y: number;
};

For more examples for this practice, please read this.

React

Function Components

React Function Components (also known as React Functional Components) are the status quo of writing modern React applications. In the past, there have been various React Component Types, but with the introduction of React Hooks it's possible to write your entire application with just functions as React components.

Since React Hooks have been introduced in React, Function Components are not anymore behind Class Components feature-wise. You can have state, side-effects and lifecycle methods in React Function Components now.

Function Components are more lightweight than Class Components and offer a sophisticated API for reusable yet encapsulated logic with React Hooks.

For the sake of comparison, check out the implementation of the following Class Component vs Functional Component:

/* Class Component */

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value: localStorage.getItem('myValueInLocalStorage') || ''
        };
    }

    componentDidUpdate() {
        localStorage.setItem('myValueInLocalStorage', this.state.value);
    }

    onChange = (event) => {
        this.setState({ value: event.target.value });
    };

    render() {
        return (
            <div>
                <h1>Hello React ES6 Class Component!</h1>

                <input value={this.state.value} type="text" onChange={this.onChange} />

                <p>{this.state.value}</p>
            </div>
        );
    }
}

/* Function Component */

const App = () => {
    const [value, setValue] = React.useState(localStorage.getItem('myValueInLocalStorage') || '');

    React.useEffect(() => {
        localStorage.setItem('myValueInLocalStorage', value);
    }, [value]);

    const onChange = (event) => setValue(event.target.value);

    return (
        <div>
            <h1>Hello React Function Component!</h1>

            <input value={value} type="text" onChange={onChange} />

            <p>{value}</p>
        </div>
    );
};

To learn more about React Function Components, please continue reading this article.

WordPress Configuration

URL: https://github.com/WordPress/gutenberg/tree/master/packages/scripts

Webpack Configuration

The final JavaScript files will be generated inside resources/assets/js.

The final CSS files will be generated inside resources/assets/css

Tests

Sequoia uses Jest as its test runner.

Filename Conventions

Jest will look for test files with any of the following popular naming conventions:

  • Files with .test.ts or .test.tsx suffixes (recommened).
  • Files with .spec.ts or .spec.tsx suffixes.
  • Files with .ts or .tsx suffixes in __tests__ folders.

The .test.ts/.spec.ts files (or the __tests__ folders) can be located at any depth under the app top level folder.

We recommend to put the test files (or __tests__ folders) next to the code they are testing so that relative imports appear shorter. For example, if App.test.tsx and App.tsx are in the same folder, the test only needs to import App from './App' instead of a long relative path. Collocation also helps find tests more quickly in larger projects.

Writing Tests

To create tests, add test() (or it()) blocks with the name of the test and its code. You may optionally wrap them in describe() blocks for logical grouping but this is neither required nor recommended.

Jest provides a built-in expect() global function for making assertions. A basic test could look like this:

import sum from './sum';

test('Sums numbers', () => {
    expect(sum(1, 2)).toEqual(3);
    expect(sum(2, 2)).toEqual(4);
});

All expect() matchers supported by Jest are extensively documented here.

You can also use jest.fn() and expect(fn).toBeCalled() to create "spies" or mock functions.

Sequoia uses react-testing-library which is a library for testing React components in a way that resembles the way the components are used by end users. It is well suited for unit, integration, and end-to-end testing of React components and applications. It works more directly with DOM nodes, and therefore it's recommended to use with jest-dom for improved assertions.

import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';

test('Renders welcome message', () => {
    render(<App />);

    expect(screen.getByText('Hello World!')).toBeInTheDocument();
});

Learn more about the utilities provided by react-testing-library to facilitate testing asynchronous interactions as well as selecting form elements from the react-testing-library documentation and examples.

Sequoia also includes @testing-library/user-event when you need to test user events in the browser. @testing-library/user-event tries to simulate the real events that would happen in the browser as the user interacts with it.

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('click', () => {
    render(
        <div>
            <label htmlFor="checkbox">Check</label>
            <input id="checkbox" type="checkbox" />
        </div>
    );

    userEvent.click(screen.getByText('Check'));
    expect(screen.getByLabelText('Check')).toHaveAttribute('checked', true);
});

Readme

Keywords

Package Sidebar

Install

npm i wp-sequoia-admin-interface

Weekly Downloads

0

Version

1.0.2

License

GPL-3.0

Unpacked Size

1.27 MB

Total Files

436

Last publish

Collaborators

  • justiauser