Skip to content

Commit 07da6b1

Browse files
authored
Merge pull request #167 from joyofrails/article/color-scheme
Article: Custom color schemes demo
2 parents d1cd9fb + b95b69e commit 07da6b1

File tree

64 files changed

+944
-281
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+944
-281
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ GEM
124124
childprocess (5.0.0)
125125
code_analyzer (0.5.5)
126126
sexp_processor
127-
color_conversion (0.1.1)
127+
color_conversion (0.1.2)
128128
commonmarker (1.1.2-arm64-darwin)
129129
commonmarker (1.1.2-x86_64-linux)
130130
concurrent-ruby (1.2.3)
10.6 KB
Loading
583 KB
Loading

app/content/layouts/article.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<%= yield %>
2424
</div>
2525
</article>
26-
<section>
26+
<section id="newsletter-signup">
2727
<%= render "users/newsletter_subscriptions/banner" %>
2828
</section>
2929
<% end %>

app/content/models/article_page.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ class ArticlePage < Sitepress::Model
88
delegate :mime_type, :handler, to: :page
99

1010
def self.published
11-
all.filter { |article| article.published.present? }.sort_by { |article| article.published_on }
11+
all
12+
.filter { |article| article.published.present? }
13+
.sort { |a, b| b.published_on <=> a.published_on } # DESC order
1214
end
1315

1416
def self.draft
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Acknowledgements
3+
layout: article
4+
---
5+
6+
I’d like to first acknowledge the design, aesthetic, and philosophy of this site borrows heavily from others.
7+
8+
- [joshwcomeau.com](https://www.joshwcomeau.com/)—Josh is a talented educator and trend-setter in creating interactive content online. Though his audience may be mostly frontend developers and those interested in React, his content is highly relevant to Rails developers as well. Many of his articles embed interactive components to help teach the concepts, something I aspire to his site. Like Garrett, he is committed to accessibility. He practices what he preaches, such as when he tells his readers [You don’t need a UI framework](https://www.smashingmagazine.com/2022/05/you-dont-need-ui-framework/). And it goes without saying his successful course [Joy of React](https://www.joyofreact.com/) inspired me to start **Joy of Rails**.
9+
10+
- [garrettdimon.com](https://garrettdimon.com)—I love the simplicity of Garrett‘s site along with his commitment to accessibility. He also has "meta" content to help educate readers; not only is there a meta section of the website, but the HTML source often includes explanatory comments addressed to folks like me peeking under the hood. This is very much in spirit with what I‘d like for **Joy of Rails**. Please check out [Garrett’s site](https://garrettdimon.com/) for great content related to software development and running a SaaS business, among other topics.
11+
12+
- [writesoftwarewell.com](https://www.writesoftwarewell.com/)—It’s hard to miss Akshay’s content when you’re searching for guidance on Rails. His archive serves as a tremendous complement to the Rails guides themselves. He has a unique ability to frame concepts in a friendly and accessible way.

app/content/pages/about/design.html.mdrb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ title: Site Design
33
layout: article
44
---
55

6-
I’d like to first acknowledge the layout and aesthetic of this site borrows heavily from [garrettdimon.com](https://garrettdimon.com)—I love the simplicity of Garrett‘s site along with his commitment to accessibility. He also has "meta" content to help educate readers; not only is there a meta section of the website, but the HTML source often includes explanatory comments addressed to folks like me peeking under the hood. This is very much in spirit with what I‘d like for Joy of Rails. Please check out [Garrett’s site](https://garrettdimon.com/) for great content related to software development and running a SaaS business, among other topics.
6+
I am [not a designer](...). Though the design of this site is built largely from scratch, I’ve done my best to [acknowledge](/about/acknowledgements) others whose work has greatly influenced this site and from whom I’ve borrowed heavily.
77

8-
The design leans heavily into CSS grid. I’m a relatively new student of CSS grid so I’m sure the approach could be improved. I built a 12 column layout from using https://utopia.fyi to generate based grid, layout, and type settings as CSS variables.
8+
## Layout
99

10-
Speaking of, the color theme of the site is also driven by CSS variables. Taking a cue from Tailwind’s default color palettes, I liked the idea of building the color theme off a monochromatic scale. This led me to [`chroma-js` ](https://www.npmjs.com/package/chroma-js) which can generate a scale given seed colors. Joy of Rails can now swap out its color theme given a recognized color name. It’s currently hard-coded but a future release will allow readers to swap out the color themselves.
10+
The design leans heavily into [CSS grid](...). I’m a relatively new student of CSS grid so I’m sure the approach could be improved. I built a 12 column layout from using https://utopia.fyi to generate based grid, layout, and type settings as CSS variables. Utopia allows designers and developers to generate a mathematically consistent
1111

12-
I‘m not directly using a UI framework. The first iteration of this site’s design was built with Tailwind which has now been removed. I have copied over a number of Tailwind utility classes I used in that first iteration as well as the generated Utopia CSS variables for layout, spacing, and typograpy. I want to Joy of Rails to "own more" of its dependencies—part of why I implemented [Authentication from Scratch](https://github.com/joyofrails/joyofrails.com/pull/149). I love Tailwind, but in moving in this direction, it felt freeing to realize I [don’t need a CSS framework](https://www.smashingmagazine.com/2022/05/you-dont-need-ui-framework/). This does mean a more work is required in HTML and CSS to make styles and accessibility work better for readers.
12+
## Colors
13+
14+
Speaking of, the color theme of the site is also driven by CSS variables. I liked the idea of building the color theme off a monochromatic scale similar to [Tailwind’s color palettes](https://tailwindcss.com/docs/customizing-colors). This led me to [uicolors.app](https://uicolors.app), a Tailwind color generator. I had so much fun playing with different colors for the site, I decided to recreate that experience for you the reader. You can now visit the <%= link_to "color scheme settings", settings_color_scheme_path %> to swap out the color theme of the site. It’s currently hard-coded but a future release will allow readers to swap out the color themselves.
15+
16+
## UI Frameworks?
17+
18+
**[Joy of Rails](/)** does not currently use a UI framework, like Material or Bootstrap. The first iteration of this site’s design was built with Tailwind which I have removed. I have copied over a number of Tailwind utility classes I used in that first iteration as well as the generated Utopia CSS variables for layout, spacing, and typography. I want to Joy of Rails to "own more" of its dependencies—part of why I implemented [Authentication from Scratch](https://github.com/joyofrails/joyofrails.com/pull/149). I love Tailwind, it was empowering to embrace the idea that I [don’t need a UI framework](https://www.smashingmagazine.com/2022/05/you-dont-need-ui-framework/). . This does mean a more work is required in HTML and CSS to make styles and accessibility work better for readers.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
title: Custom color schemes with Ruby on Rails
3+
author: Ross Kaffenberger
4+
layout: article
5+
description: You can edit the color scheme of this website right in content of this blog post. Play with the controls while we highlight the benefits of Rails, Hotwire, and CSS variables.
6+
summary: You can edit the color scheme of this website right in content of this blog post. Play with the controls while we highlight the benefits of Rails, Hotwire, and CSS variables.
7+
# published:
8+
image: articles/custom-color-schemes-with-ruby-on-rails/rainbows.jpg
9+
uuid: d99f045b-f3f7-4408-811e-9701b1a13ce8
10+
tags:
11+
- Rails
12+
---
13+
14+
This blog post lets you edit the color scheme of this site, [Joy of Rails](/).
15+
16+
Give it a try.
17+
18+
<%= turbo_frame_tag "color-scheme-form", src: settings_color_scheme_path(custom_color_scheme_params), class: "grid-cols-12 lg:grid-cols-12" %>
19+
<noscript>
20+
JavaScript not enabled? Go to the <%= link_to "color scheme demo", settings_color_scheme_path(custom_color_scheme_params) %>. Then come back when you’re done.
21+
</noscript>
22+
23+
---
24+
25+
## How it works
26+
27+
Pretty cool, huh? Here are the key ingredients:
28+
29+
- [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties): Also known as CSS custom properties
30+
- [SQLite](https://www.sqlite.org/): A `color_schemes` table to store curated color schemes
31+
- [Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies): A session cookie to store your saved color scheme selection
32+
- [Hotwire](https://hotwired.dev/): Server-rendered HTML and a single-page app experience powered by Rails, [Turbo Drive](https://turbo.hotwired.dev/handbook/introduction#turbo-drive%3A-navigate-within-a-persistent-process), and [Turbo Frames](https://turbo.hotwired.dev/handbook/introduction#turbo-frames%3A-decompose-complex-pages)
33+
34+
I started with the premise of using a monochromatic color scheme based on the 11 color scale Tailwind uses for each of [its default color sets](https://tailwindcss.com/docs/customizing-colors). The curated options are all generated from [uicolors.app](https://uicolors.app/create).
35+
36+
Each color scheme is a row in the `color_schemes` table, with a name and CSS hex code values for each of the eleven weights.
37+
38+
```ruby:{"filename":"db/migrate/<timestamp>_create_color_schemes.rb"}
39+
class CreateColorSchemes < ActiveRecord::Migration[7.1]
40+
def change
41+
create_table :color_schemes do |t|
42+
t.string :name, null: false, index: {unique: true}
43+
t.string :weight_50, null: false
44+
t.string :weight_100, null: false
45+
t.string :weight_200, null: false
46+
# ... and so on
47+
end
48+
end
49+
end
50+
```
51+
52+
CSS variables make it easy to change repeated CSS in a lot of places. You can set a CSS variable with double dashes, `--`. The CSS variable can be accessed using the `var()` expression. CSS variables can be overridden and can be defined in terms of other variables.
53+
54+
Here’s a simplified a view of how I used CSS variables to define the main background color of the `<body>` element.
55+
56+
```css:{"show_header": false}
57+
:root {
58+
--theme-color-50: var(--my-color-50, var(--default-color-50));
59+
--theme-color-100: var(--my-color-100, var(--default-color-100));
60+
--theme-color-200: var(--my-color-200, var(--default-color-200));h
61+
/* and so on */
62+
}
63+
64+
body {
65+
background-color: var(--theme-color-50);
66+
}
67+
```
68+
69+
Even though Joy of Rails does not use Tailwind, this approach is consistent with [the Tailwind docs for using CSS variables to customize Tailwind colors](https://tailwindcss.com/docs/customizing-colors#using-css-variables).
70+
71+
When you click the "Save" button, the application stores the `id` of the chosen color scheme in your Rails session:
72+
73+
```ruby:{"show_header":false}
74+
session[:color_scheme_id] = @color_scheme.id
75+
```
76+
77+
When the page is rendered, the application checks for the presence of the session data to query for the desired color scheme:
78+
79+
```ruby:{"show_header":false}
80+
if session[:color_scheme_id]
81+
@color_scheme = ColorScheme.find(session[:color_scheme_id])
82+
end
83+
```
84+
85+
The color scheme CSS variables can be written directly into a style tag
86+
87+
```erb:{"filename":"app/view/layouts/application.html.erb"}
88+
<style>
89+
:root {
90+
--my-color-50: <%= @color_scheme.weight_50.to_json %>;
91+
--my-color-100: <%= @color_scheme.weight_100.to_json %>;
92+
--my-color-200: <%= @color_scheme.weight_200.to_json %>;
93+
/* and so on */
94+
}
95+
</style>
96+
```
97+
98+
Most of the time-consuming work required to assemble this post was in researching the color palettes and building the site theme with various background and text colors defined in CSS variables.
99+
100+
## Progressively enhanced
101+
102+
Did you notice how the color choice updated automatically in place? At least, it should have for visitors that have JavaScript enabled.
103+
104+
<noscript>
105+
<p>
106+
<em>I can see you don’t have JavaScript enabled! That’s okay! The color schemes preview should have at still worked, which might not have been true had I used a JavaScript framework that relies on client-side rendering.</em>
107+
</p>
108+
</noscript>
109+
110+
One of the things I love about Rails and Hotwire is that it’s possible to adopt **progressive enhancement**.
111+
112+
> Progressive enhancement is a design philosophy that provides a baseline of essential content and functionality to as many users as possible, while delivering the best possible experience only to users of the most modern browsers that can run all the required code.
113+
>
114+
> [Source: MDN Glossary](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement)
115+
116+
In this case, visitors who don’t have JavaScript enabled would see a link to the the <%= link_to "color schemes settings page", settings_color_scheme_path %>. That page has most of the functionality you see here.
117+
118+
This design is made possible by the fact that the color scheme preview HTML is all rendered on the server with Ruby on Rails. With JavaScript enabled, Turbo JavaScript "upgrades" the default browser form submissions and link clicks to AJAX requests and use the HTML response to replace the parts of the page in place. Without JavaScript, visitors will still get the desired results after a full page navigation and/or redirects.
119+
120+
Progressive enhancement hasn’t necessarily been a hot topic alongside the popular JavaScript frameworks that rely on client-side rendering. It’s nice to see that most of what you see on these pages is static content and shouldn’t require a lot of code running in your browser to view.
121+
122+
## Look at all the things I’m not doing
123+
124+
Notice how I do not mention any of the major JavaScript frameworks in the list of tools I used to build custom color schemes.
125+
126+
In fact, I wrote only a one line of custom JavaScript to make the color scheme preview selection work: on an `onchange` handler:
127+
128+
```html:{"show_header": false}
129+
<select onchange="this.form.requestSubmit()">
130+
<!-- options -->
131+
</select>
132+
```
133+
134+
This isn’t to say I don’t like writing JavaScript or that I don’t think you should use JavaScript. I’m not here to tell you that you shouldn’t use React. Or Vue, Svelte, Solid, or Angular. Personally, I think JavaScript and the popular JavaScript frameworks are great. I recognize why and how they have become so popular.
135+
136+
I’m also not here to tell you that Rails with Hotwire is objectively better than any of these JavaScript frameworks or similar options.
137+
138+
But—and this is the important point—I didn’t _need_ any client-side rendering or JavaScript component library to make this work. It _is_ possible to build a single-page application experience in Rails. It’s not always necessary to build a single-page application in JavaScript.
139+
140+
For a solo developer with a busy home life and a full-time job, saving time and energy for other important tasks, like writing, with a language and framework I love; this makes all the difference.
141+
142+
---
143+
144+
Would you like to see an in-depth tutorial on how to build the custom color scheme functionality? Did you find a bug or do you have questions about the content? Please [send me an email](mailto:[email protected]). You can also connect with me on [Twitter](https://twitter.com/rossta), [Github](https://github.com/rossta), [Mastodon](https://ruby.social/@rossta), and [Linkedin](https://www.linkedin.com/in/rosskaffenberger).
145+
146+
Curious to peek behind the curtain and get a glimpse of the magic? [Joy of Rails is open source on Github](https://github.com/joyofrails/joyofrails.com). Feel free to look through the code and contribute.
147+
148+
And if I’ve captured your interest, please <%= link_to "subscribe", "#newsletter-signup" %> to hear more from me and get notified of new articles by email.
149+
150+
That does it for another glimpse into what’s possible with Ruby on Rails. I hope you enjoyed it.
151+
152+
![Rainbow](articles/custom-color-schemes-with-ruby-on-rails/rainbows.jpg 'Rainbow, by Jasper (my son)')

app/controllers/concerns/color_scheming.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module ColorScheming
44
included do
55
helper_method :custom_color_scheme_params
66
helper_method :custom_color_scheme?
7+
helper_method :find_color_scheme
8+
helper_method :preview_color_scheme
9+
helper_method :session_color_scheme
10+
helper_method :default_color_scheme
711
end
812

913
def custom_color_scheme_params = preview_color_scheme_id ? {settings: {color_scheme_id: preview_color_scheme_id}} : {}
@@ -12,16 +16,16 @@ def custom_color_scheme?
1216
preview_color_scheme_id.present? || session_color_scheme_id.present?
1317
end
1418

15-
def preview_color_scheme_id = params.dig(:settings, :color_scheme_id)
16-
17-
def session_color_scheme_id = session[:color_scheme_id]
18-
1919
def find_color_scheme
2020
preview_color_scheme || session_color_scheme || default_color_scheme
2121
end
2222

23+
def preview_color_scheme_id = params.dig(:settings, :color_scheme_id)
24+
2325
def preview_color_scheme = preview_color_scheme_id && ColorScheme.find(preview_color_scheme_id)
2426

27+
def session_color_scheme_id = session[:color_scheme_id]
28+
2529
def session_color_scheme = session_color_scheme_id && ColorScheme.find(session_color_scheme_id)
2630

2731
def default_color_scheme = @default_color_scheme ||= ColorScheme.cached_default

app/controllers/settings/color_schemes_controller.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ def show
66
format.html {
77
render ColorSchemes::ShowView.new(
88
settings: Settings.new(color_scheme: @color_scheme),
9-
curated_color_schemes: ColorScheme.cached_curated,
109
preview_color_scheme: preview_color_scheme,
1110
session_color_scheme: session_color_scheme,
1211
default_color_scheme: default_color_scheme

0 commit comments

Comments
 (0)