Homegrown React Libraries

Jeanmarie Jackman
5 min readJan 26, 2021

--

While working on my React project, I researched several npm calendar libraries, including one of which I implemented. But also, came to discover a way that one can grow one’s own garden of npm libraries and save time for future projects.

Start by creating a new react app:

npx create-react-app component-library

Then delete everything inside src, and create a new index.js

//index.jsimport React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<div>Hello world</div>, document.getElementById("root"));

Add a new folder into src, called lib, and then all the components you make can be published to npm from there, for example:

//src/lib/LogCalendar.js'import React from "react";
import { format, addDays, addMonths, subMonths, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isSameMonth, isSameDay, toDate } from 'date-fns'
class LogCalendar extends React.Component {state = {
currentMonth: new Date(),
selectedDate: new Date()
};
renderHeader() {
const dateFormat = "MMMM yyyy";
return (
<div className="header row flex-middle">
<div className="col col-start">
<div className="icon" onClick={this.prevMonth}>
chevron_left
</div>
</div>
<div className="col col-center">
<span>
{format(this.state.currentMonth, dateFormat)}
</span>
</div>
<div className="col col-end" onClick={this.nextMonth}>
<div className="icon">chevron_right</div>
</div>
</div>
);
}

renderDays() {
const dateFormat = "ddd";
const days = [];
let startDate = startOfWeek(this.state.currentMonth);
for (let i = 0; i < 7; i++) {
days.push(
<div className="col col-center" key={i}>
{format(addDays(startDate, i), dateFormat)}
</div>
);
}
return <div className="days row">{days}</div>;
}
renderCells() {
const { currentMonth, selectedDate } = this.state;
const monthStart = startOfMonth(currentMonth);
const monthEnd = endOfMonth(monthStart);
const startDate = startOfWeek(monthStart);
const endDate = endOfWeek(monthEnd);

const dateFormat = "d";
const rows = [];

let days = [];
let day = startDate;
let formattedDate = "";

while (day <= endDate) {
for (let i = 0; i < 7; i++) {
formattedDate = format(day, dateFormat);
const cloneDay = day;
days.push(
<div
className={`col cell ${
!isSameMonth(day, monthStart)
? "disabled"
: isSameDay(day, selectedDate) ? "selected" : ""
}`}
key={day}
onClick={() => this.onDateClick(toDate(cloneDay))}
>
<span className="number">{formattedDate}</span>
<span className="bg">{formattedDate}</span>
</div>
);
day = addDays(day, 1);
}
rows.push(
<div className="row" key={day}>
{days}
</div>
);
days = [];
}
return <div className="body">{rows}</div>;
}

onDateClick = day => {
this.setState({
selectedDate: day
});
};
nextMonth = () => {
this.setState({
currentMonth: addMonths(this.state.currentMonth, 1)
});
};

prevMonth = () => {
this.setState({
currentMonth: subMonths(this.state.currentMonth, 1)
});
};
render() {
return (
<div className="calendar">
{this.renderHeader()}
{this.renderDays()}
{this.renderCells()}
</div>
);
}
}
export default LogCalendar;

You can style it inline or add an import css file:

/* src/lib/LogCalendar.css */.App {
text-align: center;
}

/* FONT IMPORTS */

@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,700);
@import url(https://fonts.googleapis.com/icon?family=Material+Icons);

.icon {
font-family: 'Material Icons', serif;
font-style: normal;
display: inline-block;
vertical-align: middle;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;

-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'liga';
}


/* VARIABLES */

:root {
--main-color: #1a8fff;
--text-color: #777;
--text-color-light: #ccc;
--border-color: #eee;
--bg-color: #f9f9f9;
--neutral-color: #fff;
}


/* GENERAL */

* {
box-sizing: border-box;
}

body {
font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
font-size: 1em;
font-weight: 300;
line-height: 1.5;
color: var(--text-color);
background: var(--bg-color);
position: relative;
}

header {
display: block;
width: 100%;
padding: 1.75em 0;
border-bottom: 1px solid var(--border-color);
background: var(--neutral-color);
}

header #logo {
font-size: 175%;
text-align: center;
color: var(--main-color);
line-height: 1;
}

header #logo .icon {
padding-right: .25em;
}

main {
display: block;
margin: 0 auto;
margin-top: 5em;
max-width: 50em;
}

.container {
flex: 1;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-evenly;
}
/* GRID */

.row {
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}

.row-middle {
align-items: center;
}

.col {
flex-grow: 1;
flex-basis: 0;
max-width: 100%;
}

.col-start {
justify-content: flex-start;
text-align: left;
}

.col-center {
justify-content: center;
text-align: center;
}

.col-end {
justify-content: flex-end;
text-align: right;
}


/* Calendar */

.calendar {
display: block;
position: relative;
width: 100%;
background: var(--neutral-color);
border: 1px solid var(--border-color);
}

.calendar .header {
text-transform: uppercase;
font-weight: 700;
font-size: 115%;
padding: 1.5em 0;
border-bottom: 1px solid var(--border-color);
}

.calendar .header .icon {
cursor: pointer;
transition: .15s ease-out;
}

.calendar .header .icon:hover {
transform: scale(1.75);
transition: .25s ease-out;
color: var(--main-color);
}

.calendar .header .icon:first-of-type {
margin-left: 1em;
}

.calendar .header .icon:last-of-type {
margin-right: 1em;
}

.calendar .days {
text-transform: uppercase;
font-weight: 400;
color: var(--text-color-light);
font-size: 70%;
padding: .75em 0;
border-bottom: 1px solid var(--border-color);
}

.calendar .body .cell {
position: relative;
height: 5em;
border-right: 1px solid var(--border-color);
overflow: hidden;
cursor: pointer;
background: var(--neutral-color);
transition: 0.25s ease-out;
}

.calendar .body .cell:hover {
background: var(--bg-color);
transition: 0.5s ease-out;
}

.calendar .body .selected {
border-left: 10px solid transparent;
border-image: linear-gradient(45deg, #1a8fff 0%,#53cbf1 40%);
border-image-slice: 1;
}

.calendar .body .row {
border-bottom: 1px solid var(--border-color);
}

.calendar .body .row:last-child {
border-bottom: none;
}

.calendar .body .cell:last-child {
border-right: none;
}

.calendar .body .cell .number {
position: absolute;
font-size: 82.5%;
line-height: 1;
top: .75em;
right: .75em;
font-weight: 700;
}

.calendar .body .disabled {
color: var(--text-color-light);
pointer-events: none;
}

.calendar .body .cell .bg {
font-weight: 700;
line-height: 1;
color: var(--main-color);
opacity: 0;
font-size: 8em;
position: absolute;
top: -.2em;
right: -.05em;
transition: .25s ease-out;
letter-spacing: -.07em;
}

.calendar .body .cell:hover .bg, .calendar .body .selected .bg {
opacity: 0.05;
transition: .5s ease-in;
}

.calendar .body .col {
flex-grow: 0;
flex-basis: calc(100%/7);
width: calc(100%/7);
}

Then, you can publish to npm from src/index.js

//src/lib/index.jsimport TextInput from "./TextInput"; export { TextInput };

To get ready to publish to npm, you also need to create babel-cli using the command npm i babel-cli --save-dev and create a file .babelrc in the root of the project with the following contents:

{
"presets": [["react-app", { "absoluteRuntime": false }]]
}

Then in order to get npm run buildto be able to work, inside the package.json, replace the build script with

"build": "rm -rf dist && NODE_ENV=production babel src/lib --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__"

remove from package.json `"main": "dist/index.js", "module": "dist/index.js", "files": [ "dist", "README.md" ], "repository": { "type": "git", "url": "URL_OF_YOUR_REPOSITORY" } and then add to package.json

"main": "dist/index.js",
"module": "dist/index.js",
"files": [ "dist", "README.md" ],
"repository": {
"type": "git",
"url": "URL_OF_YOUR_REPOSITORY"
}

Then delete the readme text, and add what you need to explain your component:

# component-library
A library of React components created using `create-react-app`.
## Installation
Run the following command:
`npm install component-library`
and
`npm install date-fns --save
# or
yarn add date-fns` to add the required date-picker utility

Then, finally, publish to npm by running the command:npm run publish and boom! now you can install into any future Create React app using command `npm install component-library`

--

--

Jeanmarie Jackman

Full Stack Developer | Software Engineer | React | Ruby on Rails | Musician | Artist | Educator