https://github.com/ntamvl/rails7withreacttailwindcssbootstrapexample
Rails 7 with React, TailwindCSS and Bootstrap 5 Example
https://github.com/ntamvl/rails7withreacttailwindcssbootstrapexample
bootstrap5 rails rails7 react tailwindcss
Last synced: 4 months ago
JSON representation
Rails 7 with React, TailwindCSS and Bootstrap 5 Example
- Host: GitHub
- URL: https://github.com/ntamvl/rails7withreacttailwindcssbootstrapexample
- Owner: ntamvl
- Created: 2021-12-21T08:19:38.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-08-04T15:49:58.000Z (almost 3 years ago)
- Last Synced: 2025-01-14T11:14:14.106Z (4 months ago)
- Topics: bootstrap5, rails, rails7, react, tailwindcss
- Language: HTML
- Homepage: https://ntam.me/rails-7-react-tailwindcss-bootstrap-5-example/
- Size: 4.11 MB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Rails 7 with React, TailwindCSS and Bootstrap 5 Example
Rails 7 with React, TailwindCSS and Bootstrap 5 Example
- Ruby 3.0.3
- Rails 7.0.0
- SQLite
- Node v14.15.0
- NPM 6.14.8
- Yarn 1.22.17
- TailwindCSS 3
- Bootstrap 5
- React 17.0.2## Setup Rails 7 project
**Create Rails 7 project**
```bash
rails _7.0.0_ new Rails7WithReactTailwindCSSBootstrapExample -j esbuild -c tailwind
```**Install node packages**
```bash
yarn add @tailwindcss/forms @tailwindcss/typography bootstrap @popperjs/core jquery postcss-flexbugs-fixes postcss-import postcss-nested postcss-preset-env react react-dom prop-types
```**Create PostCSS config file `postcss.config.js`**
```js
module.exports = {
plugins: [
require("autoprefixer"),
require("postcss-import"),
require("tailwindcss"),
require("postcss-nested"),
require("postcss-flexbugs-fixes"),
require("postcss-preset-env")({
autoprefixer: {
flexbox: "no-2009",
},
stage: 3,
}),
],
};```
**Create esbuild plugin to load css file `esbuild.style.loader.plugin.js`**
```js
// esbuild.style.loader.plugin.jsconst fs = require('fs');
const styleLoaderPlugin = {
name: 'styleLoader',
setup: build => {
// replace CSS imports with synthetic 'loadStyle' imports
build.onLoad({ filter: /\.css$/ }, async args => {
return {
contents: `
import {loadStyle} from 'loadStyle';
loadStyle(${JSON.stringify(args.path)});
`,
loader: 'js',
};
});// resolve 'loadStyle' imports to the virtual loadStyleShim namespace which is this plugin
build.onResolve({ filter: /^loadStyle$/ }, args => {
return { path: `loadStyle(${JSON.stringify(args.importer)})`, namespace: 'loadStyleShim' };
});// define the loadStyle() function that injects CSS as a style tag
build.onLoad({ filter: /^loadStyle\(.*\)$/, namespace: 'loadStyleShim' }, async args => {
const match = /^loadStyle\(\"(.*)"\)$/.exec(args.path);
const cssFilePath = match[1];
const cssFileContents = String(fs.readFileSync(cssFilePath));
return {
contents: `
export function loadStyle() {
const style = document.createElement('style');
style.innerText = \`${cssFileContents}\`;
document.querySelector('head').appendChild(style);
}
`,
};
});
},
};module.exports = {
styleLoaderPlugin
};```
**Create file `esbuild.config.js`**
```js
// esbuild.config.jsconst path = require('path')
const { styleLoaderPlugin } = require("./esbuild.style.loader.plugin");require("esbuild").build({
entryPoints: [
'application.js',
'react/hello_react.js',
'styles/index.css'
],
bundle: true,
logLevel: 'info',
outdir: path.join(process.cwd(), "app/assets/builds"),
absWorkingDir: path.join(process.cwd(), "app/javascript"),
watch: process.argv.includes("--watch"),
publicPath: '/assets',
loader: {
'.js': 'jsx',
'.png': 'file'
},
plugins: [
styleLoaderPlugin
],
}).catch(() => process.exit(1))```
**Add build script to `package.json`**
```json
// ...
"scripts": {
"build": "node esbuild.config.js",
"build:css": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css"
},
// ...
```**Generate tailwindcss config**
```
rm tailwind.config.js
npx tailwindcss init --full
```**Update Tailwind config `tailwind.config.js`**
```js
// ...
content: [
'./app/views/**/*.html.erb',
'./app/helpers/**/*.rb',
'./app/javascript/**/*.js'
],
prefix: 'tw-',
// ...
```**Create custom styles `app/assets/stylesheets/styles.css`**
```css
/* app/assets/stylesheets/styles.css */
/* Custom Styles */.my-styles {
font-weight: 600;
color: green;
}```
**Update file `application.tailwind.css`**
```css
/* this line is used for the case if prioritizes Bootstrap first */
@import "bootstrap/dist/css/bootstrap.css";@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";/* this line is used for the case if prioritizes Bootstrap later */
/* @import "bootstrap/dist/css/bootstrap.css"; */
@import "./styles";```
Copy images to `app/assets/images/` base on this project
- app/assets/images/beams.jpeg
- app/assets/images/grid.svg
- app/assets/images/logo.svg## Generate home, hello_react pages
```
./bin/rails g controller pages home hello_react```
## Update `route.rb`
```rb
Rails.application.routes.draw do
root 'pages#home'
get '/hello_react' => 'pages#hello_react'
end
```## Setup Bootstrap and jQuery
```
mkdir app/javascript/libs
touch app/javascript/libs/bootstrap.js
touch app/javascript/libs/jquery.js
touch app/javascript/libs/index.js
```**Create bootstrap config `app/javascript/libs/bootstrap.js`**
```js
// app/javascript/libs/bootstrap.js// import "bootstrap/dist/css/bootstrap.css"
// import "bootstrap/dist/js/bootstrap.bundle.js"const bootstrap = require("bootstrap/dist/js/bootstrap.bundle.js")
const popoverElements = document.querySelector('[data-bs-toggle="popover"]')
if (popoverElements) new bootstrap.Popover(popoverElements, { trigger: 'hover' })```
**Create jQuery config `app/javascript/libs/jquery.js`**
```js
// app/javascript/libs/jquery.js
import jquery from 'jquery';
window.jQuery = jquery;
window.$ = jquery;```
Create file index.js to include bootstrap and jquery `app/javascript/libs/index.js`
```js
// app/javascript/libs/index.js
import "./jquery";
import "./bootstrap";
```Update `app/javascript/application.js`
```js
// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"
import "./libs"```
## Create Tailwind components
I use tailwind prefix `tw-` for this example. I have created a simple tool to generate tailwind prefix, link: http://tailwind-prefix-generator.ntam.me/
----
app/views/pages/_content_home_tw.html.erb
```html
![]()
![]()
An advanced online playground for Tailwind CSS, including support for things like:
Customizing your
tailwind.config.js
file
Extracting classes with
@apply
Code completion with instant preview
Perfect for learning how the framework works, prototyping a new idea, or creating a demo to share online.
My Styles
```
app/views/pages/_content_tailwind_1.html.erb
```html
shadow-cyan-500/50
Subscribe
shadow-blue-500/50
Subscribe
shadow-indigo-500/50
Subscribe
```
app/views/pages/_content_tailwind_2.html.erb
```html
snap point
![]()
![]()
![]()
![]()
![]()
![]()
```
app/views/pages/_content_tailwind_3.html.erb
```html
Scroll in the tw-grid of images to see the expected behaviour
snap point
![]()
![]()
![]()
![]()
![]()
![]()
```
## Create Bootstrap components
app/views/pages/_nav_bootstrap.html.erb
```html```
## Create React component
app/javascript/react/components/MyClock/styles.css
```css
.Clock {
padding: 5px;
margin-top: 15px;
margin-left: auto;
margin-right: auto;
}
```app/javascript/react/components/MyClock/MyClock.js
```js
import React, { Component } from 'react'
import PropTypes from 'prop-types';export class MyClock extends Component {
render() {
return (
);
}
}export default MyClock
export class Clock extends Component {
constructor(props) {
super(props);this.state = { time: new Date() };
this.radius = this.props.size / 2;
this.drawingContext = null;
this.draw24hour = this.props.timeFormat.toLowerCase().trim() === "24hour";
this.drawRoman = !this.draw24hour && this.props.hourFormat.toLowerCase().trim() === "roman";}
componentDidMount() {
this.getDrawingContext();
this.timerId = setInterval(() => this.tick(), 1000);
}componentWillUnmount() {
clearInterval(this.timerId);
}getDrawingContext() {
this.drawingContext = this.refs.clockCanvas.getContext('2d');
this.drawingContext.translate(this.radius, this.radius);
this.radius *= 0.9;
}tick() {
this.setState({ time: new Date() });
const radius = this.radius;
let ctx = this.drawingContext;
this.drawFace(ctx, radius);
this.drawNumbers(ctx, radius);
this.drawTicks(ctx, radius);
this.drawTime(ctx, radius);
}drawFace(ctx, radius) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();const grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, "#333");
grad.addColorStop(0.5, "white");
grad.addColorStop(1, "#333");
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();ctx.beginPath();
ctx.arc(0, 0, radius * 0.05, 0, 2 * Math.PI);
ctx.fillStyle = "#333";
ctx.fill();
}drawNumbers(ctx, radius) {
const romans = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"];
const fontBig = radius * 0.15 + "px Arial";
const fontSmall = radius * 0.075 + "px Arial";
let ang, num;ctx.textBaseline = "middle";
ctx.textAlign = "center";
for (num = 1; num < 13; num++) {
ang = num * Math.PI / 6;
ctx.rotate(ang);
ctx.translate(0, -radius * 0.78);
ctx.rotate(-ang);
ctx.font = fontBig;
ctx.fillStyle = "black";
ctx.fillText(this.drawRoman ? romans[num - 1] : num.toString(), 0, 0);
ctx.rotate(ang);
ctx.translate(0, radius * 0.78);
ctx.rotate(-ang);// Draw inner numerals for 24 hour time format
if (this.draw24hour) {
ctx.rotate(ang);
ctx.translate(0, -radius * 0.60);
ctx.rotate(-ang);
ctx.font = fontSmall;
ctx.fillStyle = "red";
ctx.fillText((num + 12).toString(), 0, 0);
ctx.rotate(ang);
ctx.translate(0, radius * 0.60);
ctx.rotate(-ang);
}
}// Write author text
ctx.font = fontSmall;
ctx.fillStyle = "#3D3B3D";
ctx.translate(0, radius * 0.30);
ctx.fillText("React Clock", 0, 0);
ctx.translate(0, -radius * 0.30);
}drawTicks(ctx, radius) {
let numTicks, tickAng, tickX, tickY;for (numTicks = 0; numTicks < 60; numTicks++) {
tickAng = (numTicks * Math.PI / 30);
tickX = radius * Math.sin(tickAng);
tickY = -radius * Math.cos(tickAng);ctx.beginPath();
ctx.lineWidth = radius * 0.010;
ctx.moveTo(tickX, tickY);
if (numTicks % 5 === 0) {
ctx.lineTo(tickX * 0.88, tickY * 0.88);
} else {
ctx.lineTo(tickX * 0.92, tickY * 0.92);
}
ctx.stroke();
}
}drawTime(ctx, radius) {
const now = this.state.time;
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();// hour
hour %= 12;
hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60));
this.drawHand(ctx, hour, radius * 0.5, radius * 0.05);
// minute
minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60));
this.drawHand(ctx, minute, radius * 0.8, radius * 0.05);
// second
second = (second * Math.PI / 30);
this.drawHand(ctx, second, radius * 0.9, radius * 0.02, "red");
}drawHand(ctx, position, length, width, color) {
color = color || "black";
ctx.beginPath();
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.moveTo(0, 0);
ctx.rotate(position);
ctx.lineTo(0, -length);
ctx.stroke();
ctx.rotate(-position);
}render() {
return (
);
}
}Clock.defaultProps = {
size: 400, // size in pixels => size is length & width
timeFormat: "24hour", // {standard | 24hour} => if '24hour', hourFormat must be 'standard'
hourFormat: "standard" // {standard | roman}
};Clock.propTypes = {
size: PropTypes.number,
timeFormat: PropTypes.string,
hourFormat: PropTypes.string
};```
app/javascript/react/components/App/styles.css
```css
.my_styles_3 {
font-family: 600;
font-size: 2rem;
color: pink;
text-align: center;
}
```app/javascript/react/components/App/index.js
```js
import React from 'react'
import MyClock from '../MyClock/MyClock'
import "./styles.css";export const App = () => {
return (
Hello React!!!
)
}export default App
```
app/javascript/react/hello_react.js
```js
import React from "react";
import { render } from "react-dom";
import App from "./components/App";document.addEventListener("DOMContentLoaded", () => {
render(, document.getElementById('react-components'));
});```
## Render Tailwind and Bootstrap 5 components
Update file `app/views/pages/home.html.erb`
```html
<%= render 'pages/nav_bootstrap' %>
<%= render 'pages/content_home_tw' %>
```## Render React components
Update file `app/views/pages/hello_react.html.erb`
```html
<%= render 'pages/nav_bootstrap' %>
<%= render 'pages/popover_bs' %>
React Components
<%= javascript_include_tag "react/hello_react" %>
Tailwind
<%= render 'pages/content_tailwind_1' %>
<%= render 'pages/content_tailwind_2' %>
<%= render 'pages/content_tailwind_3' %>
```
app/javascript/styles/index.css
```css
.my-styles-2 {
font-weight: 600;
color: red;
}
```## If you want to use SCSS/SASS
Add package `esbuild-plugin-sass`
```bash
yarn add esbuild-plugin-sass
```Update `esbuild.config.js`
```js
// ....
const sassPlugin = require('esbuild-plugin-sass')// ...
plugins: [
styleLoaderPlugin,
sassPlugin()
],// ...
```
And then you can create file like `styles.scss` and import it
Example:```scss
// styles2.scss
.my_styles_4 {
font-family: 600;
font-size: 2rem;
color: red;
text-align: center;
cursor: pointer;
&:hover {
color: green;
}
}
``````js
import React from 'react'
import MyClock from '../MyClock/MyClock'
import "./styles2.scss";export const App = () => {
return (
Hello React!!! 000
Styles SCSS
)
}
```## Create Article
```bash
./bin/rails g scaffold Article title:string body:text
./bin/rails db:create db:migrate
```*Use Tailwind and Bootstrap to update styles for article pages*
## Run app
```bash
./bin/dev
```Then go to http://localhost:3000/
OR clone source and run to see the result:
```bash
cd
git clone https://github.com/ntamvl/Rails7WithReactTailwindCSSBootstrapExample.git
cd Rails7WithReactTailwindCSSBootstrapExample
bundle install
yarn install
./bin/dev
```Screenshots:

Enjoy ^_^ :))
---
References:
- https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision
- https://getbootstrap.com/docs/5.1/getting-started/introduction/
- https://tailwindcss.com/docs/installation