PACKAGEJS: Changing the default theme to TailwindCSS

Hey again! In the previous blog, I guided you through automatically generating our interface for the to-do app. This time, I'll provide a step-by-step tutorial on changing our default theme to TailwindCSS.

Changing our default theme file directory

First, in our public folder, create a theme folder.

Then at the root of our to-do-interface_development folder, open the package.json file.

Look for this section:

"dependencies": {
    "cmpa-d3339e7f6947f2996be9f5febf377c6f": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/patch",
    "cmpa-d7ee2cbe945359db670491f7c6fbb295": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/delete",
    "cmpa-d4885ad408d784cb9d6bcd08fea11cab": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/post",
    "cmpi-d386af6ac3a67aa1bb534e90c861336a": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/item",
    "cmpl-dc13c9bea8f074cb0fe2a7e353024dc7": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/todos",
    "cmpr-d95529c47903cbc3cb36a703bd774f77": "file:/home/richgod/PackageJS/Practice/unstyled/to-do-interface_development/functions/root",
    "jsen-cls-sdk-prj-packagejs-fnc-deploy-ui-on-unix-nodejs": "git+http://localhost:49418/Repositories/packagejs-repository/jsen-cls-sdk-prj-packagejs-fnc-deploy-ui-on-unix-nodejs/1.0.0-14.18.2-compiled.git",
    "jsen-sym-bundle-sdk-for-interfaces": "git+http://localhost:49418/Projects/jsen-project/jsen-cls-sdk-mod-bundle-pkg-interface-for-reactjs-without-builder-on-localhost-use-vite-react-nodejs/1.0.0-14.18.2-compiled.git",
    "jsen-sym-bundle-app-for-interfaces": "git+http://localhost:49418/Projects/jsen-project/jsen-cls-sdk-mod-bundle-pkg-interface-for-reactjs-via-app-basic-ecmascript/18.2.0-6.0.0-compiled.git",
    "jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-state-reactjs": "git+http://localhost:49418/Repositories/packagejs-repository/jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-state-reactjs/1.0.0-18.2.0-compiled.git",
    "jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-theme-reactjs": "git+http://localhost:49418/Repositories/packagejs-repository/jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-theme-reactjs/1.0.0-18.2.0-compiled.git"
  }

Then in the last line where the key theme key value is:

"git+http://localhost:49418/Repositories/packagejs-repository/jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-theme-reactjs/1.0.0-18.2.0-compiled.git"

Change it to the file path that points to the public/theme directory you created earlier, and it will look like this:

"file:./public/theme"

Now you've done all that, it's time to install TailwindCSS.

Installing TailwindCSS

Now, add these to our dependencies in our package.json file:

{
    "dependencies": {
        "jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-theme-reactjs": "file:./public/theme",
        "autoprefixer": "^10.4.14",
        "postcss": "^8.4.23",
        "tailwindcss": "3.3.1"
    }
}

After doing that, make sure we're connected to the internet and run npm install in our terminal to install those dependencies you just added.

Next, we'll add these to our tailwind.config.js file:

module.exports = {
    content: [
        "./index.html",
        "./functions/**/*.{js,ts,jsx,tsx,css}",
        "./public/**/*.{js,ts,jsx,tsx,css}"
    ],
    theme: {},
    plugins: []
};

And then create a new file in the root directory called postcss.config.js and type these in:

module.exports = {
    plugins: [
        require('tailwindcss'),
        require('autoprefixer')
    ]
};

Inside our theme folder, create an index.jsx file and type these in:

import React from "react";
import "./index.css";
const Component = function (...args) {
    React.Component.call(this, ...args);
    this.render = function () {
        return (<></>);
    };
    return;
};
Component.prototype = Object.create(React.Component.prototype);
Component.prototype.constructor = Component;
export default Component;

In that same folder, create an index.css file and type these in:

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

The last file we need to create in our theme folder is package.json. And, in there, we'll type these in:

{
  "name": "jsen-cls-sdk-prj-packagejs-mod-toolkit-pkg-interface-for-reactjs-cmp-theme-reactjs",
  "description": "",
  "version": "0.0.0",
  "author": {
    "name": "unlicensed"
  },
  "license": "UNLICENSED",
  "private": true,
  "main": "index.jsx",
  "dependencies": {}
}

Lastly, at the root of our public folder, create a vite.config.js file and type these in:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { viteCommonjs } from "@originjs/vite-plugin-commonjs";
import tailwindcss from "tailwindcss";

export default defineConfig({
    plugins: [viteCommonjs(), react()],
    css: {
        postcss: {
            plugins: [tailwindcss]
        }
    }
});

Styling a component

If you've followed the steps above properly, once you restart the interface server, it will look like this:

Now that we've added TailwindCSS to the PackageJS interface folder, it's time to add some Tailwind classes to the UI. But first, we'll need to update our interface in the PackageJS dashboard to use custom components (instead of the default interface components that are automatically generated).

For the custom component, we'll add the list component. To do that, click on that highlighted plus sign and choose Standard component.

Then set the connection path to $dom:list/to_do_app.tasks and click Next.

After that, set the Default component to Local file absolute path (file:).

And in the textarea with the placeholder Absolute path or URL to component file or package..., type in your desired path name for our component, and click Insert:

There! We've created our component:

Now, we'll export our interface, extract its contents, and update the interface project folder with the new files (make sure you stop the interface server first!).

CAREFUL! If you just copy and paste the package.json from the newly exported update folder into the original interface folder, we'll overwrite the tailwind dependencies you added. So we'll have to copy-paste the key-values for the:

  • theme

  • tailwind

  • postcss

  • autoprefixer

into the new package.json file, and THEN drag it into the new original interface project folder.

Now that we've updated the interface project folder, it's time to redeploy the interface (npm install, then npm run deploy).

We'll refresh the browser window and see that everything is the same, except, now we can actually edit the list component page!

On the list component page, there is HTML for the default search component that's on the page. To style that search bar (input and button), all we have to do is wrap the component with a <div></div>, and give it this TailwindCSS styling, like so:

We can also use Vanilla CSS to style our component if we want. First, we'll give the <div></div> wrapping our component a className that we'll reference, like customSearchComponentWrapper:

Then create an index.css file in the todos folder:

We have to be sure that we've imported our index.css file like so:

Then we'll add these styles to the index.css file we just created:

.customSearchComponentWrapper input {
    width: 50% !important;
    height: 3rem !important;

    padding: 0.5rem !important;
}

.customSearchComponentWrapper input,
.customSearchComponentWrapper button {
    margin-left: 0.5rem !important;
    margin-right: 0.5rem !important;

    border: 2px solid black !important;
}

.customSearchComponentWrapper button {
    height: 3rem !important;
    position: relative !important;
    padding: 0.5rem !important;
    font-size: 2rem !important;
}

.customSearchComponentWrapper form {
    width: 100% !important;
    display: flex !important;
}

.customSearchComponentWrapper form div {
    width: 100% !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
}

The next time we take a look at our To-do app in the browser, our search component should look like this:

Here is the code for the entire custom list component, in case you want to copy-paste everything:

import React from "react";
import toolkit from "jsen-sym-toolkit-for-interfaces";
import "./index.css";

const t = toolkit;
const Component = function (
    ...args
) {
    React.Component.call(this, ...args);
    t.cmp.listDefault(this);
    this.componentDidMount = async function () {
        await this.ma_mounted_default();
    };
    this.componentWillUnmount = async function () {
        await this.ma_unmount_default();
    };
    this.render = function () {
        let items = this.navigator.context.items.reduce((o, v) => {
            if (v.attributes[v.contract["kSPrimary"]].hasOwnProperty("-") !== true
                || v.attributes[v.contract["kSPrimary"]]["-"] === ""
                || v.attributes[v.contract["kSPrimary"]]["-"] === "0") {
                o.push(v);
            }
            return o;
        }, []);

        return (<div>
            <main>

                <p>
                    <small><strong>Component name:</strong></small>
                    <br />
                    <input
                        value={this.navigator.context.name}
                        type="text"
                        className="readonly"
                        readOnly
                    />
                    <small><strong>Resource path:</strong></small>
                    <br />
                    <input
                        value={this.navigator.dom.constructor.fs_build_context_path(this.navigator.context)}
                        type="text"
                        className="readonly"
                        readOnly
                    />
                </p>

                <hr />

                {Object.keys(this.navigator.context.actions).length > 0 ? (
                    <div>
                        <h3>Actions:</h3>
                        {Object.keys(this.navigator.context.actions).map((k, i) => {
                            return (<p key={"actions-" + k}>
                                <a
                                    onClick={((e) => { this.ma_go(e, this.navigator.context.actions[k]).catch(() => { }); })}
                                    href={"#" + k}
                                ><u>{k}</u></a>
                            </p>);
                        })}
                        <hr />
                    </div>
                ) : (
                    <></>
                )}

                {(this.navigator.context.actions.hasOwnProperty("GET")) ? (
                    <div>

                        <h3>Items:</h3>

                        {(this.navigator.context.actions["GET"].fields.hasOwnProperty("kJQuery.kSTerm") === true) ? (
                            <div className="customSearchComponentWrapper">
                                <t.react.searchDefault {...{
                                    "pIJSENModInterfaceClsStateExtReactJSClsDOMNavigator": this.navigator,
                                    "pIContextAction": this.navigator.context.actions["GET"]
                                }} />
                            </div>

                        ) : (
                            <></>
                        )}

                        {items.length > 0 ? (
                            <div>
                                {items.map((v, i) => {
                                    return (<small key={"items-" + i}>
                                        {v.contract["kSRoute"].length ? (
                                            <a
                                                onClick={((e) => { this.ma_go(e, v).catch(() => { }); })}
                                                href={"./" + encodeURIComponent(v.attributes[v.contract["kSPrimary"]]["="]) + "/"}
                                            ><u>{JSON.stringify(v.attributes)}</u></a>
                                        ) : (
                                            <span>{JSON.stringify(v.attributes)}</span>
                                        )}
                                    </small>);
                                })}
                            </div>
                        ) : (
                            <small><em>Nothing here yet...</em></small>
                        )}

                        {(this.navigator.context.actions["GET"].fields.hasOwnProperty("kJQuery.kSTerm") === true) ? (
                            <t.react.searchLoadmore {...{
                                "pIJSENModInterfaceClsStateExtReactJSClsDOMNavigator": this.navigator,
                                "pIContextAction": this.navigator.context.actions["GET"]
                            }} />
                        ) : (
                            <></>
                        )}

                        <hr />

                    </div>
                ) : (
                    <></>
                )}

                <p>
                    &laquo;&nbsp;<a
                        onClick={((e) => { this.ma_go(e, this.navigator.context.parent).catch(() => { }); })}
                        href="../"
                    ><u>Back</u></a>
                </p>

            </main>
        </div>);
    };
    return;
};
Component.prototype = Object.create(React.Component.prototype);
Component.prototype.constructor = Component;
Component.ma_on_navigate = t.cmp.listDefault.ma_on_navigate;
export default Component;

Conclusion

We've come to the end of this blog!

Sorry this one took longer to get out and a lot of you must've been stuck after generating your interface, I hope you find this helpful and I'll catch you in the next one!