Skip to content

Commit

Permalink
Merge pull request #12 from bdurand/dark-mode
Browse files Browse the repository at this point in the history
Add support for dark mode
  • Loading branch information
bdurand authored Oct 2, 2024
2 parents 456bf45 + f03847d commit dfef159
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 31 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 2.1.0

### Added

- Added option to specify the color scheme for the web UI when mounting the rack app to support dark mode.

### Fixed

- Times stored as strings representing the seconds since the epoch are now correctly parsed as Time objects.

## 2.0.0

### Fixed
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ mount Rack::Builder.new do
end, at: "/ultra_settings"
```

You can specify the color scheme by setting by providing the `color_scheme` option to the `UltraSettings::RackApp` class. The default color scheme is `:light`. You can also set the scheme to `:dark` or `:system`.

```ruby
UltraSettings::RackApp.new(color_scheme: :dark)
```

#### Embedding the Settings View in Admin Tools

If you prefer to embed the settings view directly into your own admin tools or dashboard, you can use the `UltraSettings::ApplicationView` class to render the settings interface within your existing views:
Expand Down Expand Up @@ -421,6 +427,25 @@ end

This approach keeps your tests clean and readable while allowing for flexible configuration management during testing.

### Rollout percentages

A common usage of configuration is to control rollout of new features by specifying a percentage value and then testing if a random number is less than it. If you implement this pattern in your configuration, then you should use something like the [consistent_random](https://github.com/bdurand/consistent_random) gem to ensure you are generating consistent values without your units of work.

```ruby
class MyServiceConfiguration < UltraSettings::Configuration
field :use_http2_percentage,
type: :float,
default: 0.0,
description: "Rollout percentage for using the new HTTP/2 driver"

# Using ConsistentRandom#rand instead of Kernel#rand to ensure that we
# get the same result within a request and don't oscillate back and forth
# every time we check if this is enabled.
def use_http2?
ConsistentRandom.new("MyServiceConfiguration.use_http2").rand < use_http2_percentage
end
end
```

## Installation

Expand All @@ -446,6 +471,24 @@ Open a pull request on [GitHub](https://github.com/bdurand/ultra_settings).

Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.

You can start a local rack server to test the web UI by running

```bash
bundle exec rackup
```

You can test with some setting set by setting environment variable used in the test configuration.

```bash
MY_SERVICE_HOST=host.example.com MY_SERVICE_TOKEN=secret bundle exec rspec
```

You can test dark mode by setting the `COLOR_SCHEME` environment variable.

```bash
COLOR_SCHEME=dark bundle exec rackup
```

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.0
2.1.0
17 changes: 9 additions & 8 deletions app/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,30 @@

.ultra-settings-table thead th {
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
border-bottom: 2px solid var(--table-border-color);
background-color: var(--table-header-bg-color);
}

.ultra-settings-table td, .ultra-settings-table th {
padding: 0.75rem;
vertical-align: top;
border-top: 1px solid #dee2e6;
border-top: 1px solid var(--table-border-color);
}

.ultra-settings-table tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, .03);
background-color: var(--alt-row-color);
}

.ultra-settings-table code {
font-family: monospace;
font-size: 0.9rem;
display: inline;
color: darkred;
color: var(--code-color);
font-weight: 600;
}

.ultra-settings-table em {
color: gray;
color: var(--em-color);
font-style: italic;
font-size: 0.9rem;
}
Expand All @@ -49,13 +50,13 @@
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
color: var(--form-control-color);
background-color: var(--form-control-bg-color);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right .75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border: 1px solid var(--form-control-border-color);
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
-webkit-appearance: none;
Expand Down
31 changes: 31 additions & 0 deletions app/application_vars.css.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<% unless color_scheme == :dark %>
.ultra-settings-configuration {
--table-header-bg-color: #fff;
--table-border-color: #dee2e6;
--alt-row-color: rgba(0, 0, 0, .05);
--form-control-color: #495057;
--form-control-bg-color: #fff;
--form-control-border-color: #ced4da;
--code-color: darkred;
--em-color: gray;
}
<% end %>
<% if color_scheme == :system %>
@media (prefers-color-scheme: dark) {
<% end %>
<% if color_scheme == :system || color_scheme == :dark %>
.ultra-settings-configuration {
--table-header-bg-color: #333;
--table-border-color: #555;
--alt-row-color: rgba(0, 0, 0, .30);
--form-control-color: #eee;
--form-control-bg-color: #666;
--form-control-border-color: #555;
--code-color: pink;
--em-color: #999;
}
<% end %>
<% if color_scheme == :system %>
}
<% end %>
2 changes: 1 addition & 1 deletion app/configuration.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<th>Notes</th>
</tr>
</thead>
<tbody>
<tbody translate="no">
<% configuration.class.fields.each do |field| %>
<% source = configuration.__source__(field.name) %>
<tr>
Expand Down
8 changes: 5 additions & 3 deletions app/layout.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
* {box-sizing:border-box;}
* {
box-sizing:border-box;
}

body {
font-family: sans-serif;
font-size: 1rem;
line-height: 1.5;
text-align: left;
color: #212529;
background-color: #ffffff;
color: var(--text-color);
background-color: var(--background-color);
margin: 0;
padding: 0;
}
Expand Down
1 change: 1 addition & 0 deletions app/layout.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<title>Application Configuration</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no email=no date=no address=no">
<style type="text/css">
<%= @layout_css %>
<%= css %>
Expand Down
19 changes: 19 additions & 0 deletions app/layout_vars.css.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<% unless color_scheme != :dark %>
:root {
--text-color: #212529;
--background-color: #ffffff;
}
<% end %>
<% if color_scheme == :system %>
@media (prefers-color-scheme: dark) {
<% end %>
<% if color_scheme == :system || color_scheme == :dark %>
:root {
--text-color: #fff;
--background-color: #333;
}
<% end %>
<% if color_scheme == :system %>
}
<% end %>
2 changes: 1 addition & 1 deletion config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ UltraSettings.add(:other)
UltraSettings.add(:namespace, "Test::NamespaceConfiguration")
UltraSettings.add(:my_service, "MyServiceConfiguration")

run UltraSettings::RackApp.new
run UltraSettings::RackApp.new(color_scheme: ENV.fetch("COLOR_SCHEME", nil))
33 changes: 22 additions & 11 deletions lib/ultra_settings/coerce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Coerce
]).freeze
# rubocop:enable Lint/BooleanSymbol

NUMERIC_REGEX = /\A-?\d+(?:\.\d+)?\z/

class << self
# Cast a value to a specific type.
#
Expand All @@ -33,13 +35,19 @@ def coerce_value(value, type)
when :float
value.is_a?(Float) ? value : value.to_s&.to_f
when :boolean
Coerce.boolean(value)
boolean(value)
when :datetime
Coerce.time(value)
time(value)
when :array
Array(value).map(&:to_s)
when :symbol
value.to_s.to_sym
when :rollout
if numeric?(value)
value.to_f
else
boolean(value)
end
else
value.to_s
end
Expand All @@ -50,13 +58,9 @@ def coerce_value(value, type)
# @param value [Object]
# @return [Boolean]
def boolean(value)
if value == false
false
elsif blank?(value)
nil
else
!FALSE_VALUES.include?(value)
end
return nil if blank?(value)

!FALSE_VALUES.include?(value)
end

# Cast a value to a Time object.
Expand All @@ -66,8 +70,9 @@ def boolean(value)
def time(value)
value = nil if value.nil? || value.to_s.empty?
return nil if value.nil?
time = if value.is_a?(Numeric)
Time.at(value)

time = if numeric?(value)
Time.at(value.to_f)
elsif value.respond_to?(:to_time)
value.to_time
else
Expand All @@ -79,9 +84,15 @@ def time(value)
time
end

# @return [Boolean] true if the value is a numeric type or a string representing a number.
def numeric?(value)
value.is_a?(Numeric) || (value.is_a?(String) && value.to_s.match?(NUMERIC_REGEX))
end

# @return [Boolean] true if the value is nil or empty.
def blank?(value)
return true if value.nil?

if value.respond_to?(:empty?)
value.empty?
else
Expand Down
2 changes: 1 addition & 1 deletion lib/ultra_settings/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def field(name, type: :string, description: nil, default: nil, default_if: nil,
secret: secret
)

class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
def #{name}
__get_value__(#{name.inspect})
end
Expand Down
5 changes: 3 additions & 2 deletions lib/ultra_settings/rack_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ module UltraSettings
# No setting values are displayed, but you should still add some
# sort of authentication if you want to use this in production.
class RackApp
def initialize
def initialize(color_scheme: nil)
@webview = nil
@color_scheme = color_scheme
end

def call(env)
Expand All @@ -19,7 +20,7 @@ def webview
if ENV.fetch("RAILS_ENV", ENV.fetch("RACK_ENV", "development")) == "development"
@webview = nil
end
@webview ||= WebView.new
@webview ||= WebView.new(color_scheme: @color_scheme)
end
end
end
21 changes: 18 additions & 3 deletions lib/ultra_settings/web_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ module UltraSettings
class WebView
attr_reader :css

def initialize
# @param color_scheme [Symbol] The color scheme to use in the UI. This can be `:light`,
# `:dark`, or `:system`. The default is `:light`.
def initialize(color_scheme: :light)
color_scheme = (color_scheme || :light).to_sym
@layout_template = erb_template("layout.html.erb")
@layout_css = read_app_file("layout.css")
@css = read_app_file("application.css")
@layout_css = layout_css(color_scheme)
@css = application_css(color_scheme)
end

def render_settings
Expand All @@ -32,5 +35,17 @@ def read_app_file(path)
def app_dir
File.expand_path(File.join("..", "..", "app"), __dir__)
end

def layout_css(color_scheme)
vars = erb_template("layout_vars.css.erb").result(binding)
css = read_app_file("layout.css")
"#{vars}\n#{css}"
end

def application_css(color_scheme)
vars = erb_template("application_vars.css.erb").result(binding)
css = read_app_file("application.css")
"#{vars}\n#{css}"
end
end
end
Loading

0 comments on commit dfef159

Please sign in to comment.