keystone_ui-colors 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +198 -0
- data/Rakefile +11 -0
- data/app/controllers/concerns/keystone_ui/colors/current_palette.rb +91 -0
- data/app/controllers/keystone_ui/colors/application_controller.rb +12 -0
- data/app/controllers/keystone_ui/colors/settings_controller.rb +53 -0
- data/app/helpers/keystone_ui/colors/palette_helper.rb +14 -0
- data/app/javascript/keystone_ui/colors/theme_settings_controller.js +50 -0
- data/app/models/keystone_ui/colors/application_record.rb +7 -0
- data/app/models/keystone_ui/colors/theme_preference.rb +47 -0
- data/app/views/keystone_ui/colors/settings/show.html.erb +99 -0
- data/config/routes.rb +5 -0
- data/lib/generators/keystone_ui/colors/install/install_generator.rb +49 -0
- data/lib/generators/keystone_ui/colors/install/templates/create_keystone_ui_colors_theme_preferences.rb.erb +15 -0
- data/lib/generators/keystone_ui/colors/update/templates/add_mode_to_keystone_ui_colors_theme_preferences.rb.erb +5 -0
- data/lib/generators/keystone_ui/colors/update/update_generator.rb +30 -0
- data/lib/keystone_ui/colors/configuration.rb +31 -0
- data/lib/keystone_ui/colors/engine.rb +23 -0
- data/lib/keystone_ui/colors/palettes.rb +72 -0
- data/lib/keystone_ui/colors/templates.rb +63 -0
- data/lib/keystone_ui/colors/version.rb +7 -0
- data/lib/keystone_ui/colors.rb +11 -0
- data/lib/keystone_ui-colors.rb +1 -0
- metadata +129 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a6fe78366470d37cb8598bfa4181561c42eca52a409dd7648246c397584daad9
|
|
4
|
+
data.tar.gz: dbbcf12bbc4fd745c85cf35a94d0608cabfa9565e7a92e87eb22a5a5faa17f01
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9764dcad37f39f954cc377abf737bad185b2ee9e1e0ce301982bd6a52d286a0069ca8513b5afb918df2411475b40040646c5e605a6c384edf8b88e3f3c5c4d9b
|
|
7
|
+
data.tar.gz: 60f59e26247a9580e2422d873f0e8cb4d4daa2b9aecd5dda755972b76f7dbfb46be9cf07bed75954fb2ee591a0699ac94285e65ebf445611a8f8443e84ada4a9
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2026 Tyler Schneider
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# KeystoneUi::Colors
|
|
2
|
+
|
|
3
|
+
A Rails engine that adds per-user color palette persistence and preset themes. Companion to [keystone_ui](https://github.com/DYB-Development/keystone_ui).
|
|
4
|
+
|
|
5
|
+
Users pick from preset themes or custom hex colors. The gem generates CSS custom properties (`--color-accent-*`, `--color-surface-*`) and injects them via a `<style>` tag -- no frontend build step required.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Ruby >= 3.1
|
|
10
|
+
- Rails >= 7.0
|
|
11
|
+
- [keystone_ui](https://github.com/DYB-Development/keystone_ui) >= 0.4.1
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add to your Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "keystone_ui-colors"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run the install generator:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bin/rails generate keystone_ui:colors:install
|
|
25
|
+
bin/rails db:migrate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This creates:
|
|
29
|
+
- A migration for `keystone_ui_colors_theme_preferences`
|
|
30
|
+
- A Stimulus controller at `app/javascript/controllers/keystone_ui/colors/theme_settings_controller.js`
|
|
31
|
+
|
|
32
|
+
### Manual setup
|
|
33
|
+
|
|
34
|
+
**1. Mount the engine** in `config/routes.rb`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
mount KeystoneUi::Colors::Engine => "/keystone_ui_colors"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**2. Include the concern** in your `ApplicationController`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
class ApplicationController < ActionController::Base
|
|
44
|
+
include KeystoneUi::Colors::CurrentPalette
|
|
45
|
+
before_action :set_current_palette
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**3. Add the style tag** to your layout (`<head>`):
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<%= keystone_palette_style_tag %>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This outputs CSS variables:
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
:root {
|
|
59
|
+
--color-accent-50: #eff6ff;
|
|
60
|
+
--color-accent-100: #dbeafe;
|
|
61
|
+
/* ... through 950 */
|
|
62
|
+
--color-surface-50: #f8fafc;
|
|
63
|
+
/* ... through 950 */
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**4. Register the Stimulus controller** in `app/javascript/controllers/index.js`:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import ThemeSettingsController from "./keystone_ui/colors/theme_settings_controller"
|
|
71
|
+
application.register("keystone-ui--colors--theme-settings", ThemeSettingsController)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Create `config/initializers/keystone_ui_colors.rb`:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
KeystoneUi::Colors.configure do |config|
|
|
80
|
+
config.owner_class_name = "User" # Model that owns preferences
|
|
81
|
+
config.current_owner_method = :current_user # Controller method for current user
|
|
82
|
+
config.default_template = :ocean # Fallback theme
|
|
83
|
+
config.default_accent = "blue" # Fallback accent color
|
|
84
|
+
config.default_surface = "zinc" # Fallback surface color
|
|
85
|
+
config.default_mode = "light" # Fallback theme mode: "light", "dark" or "system"
|
|
86
|
+
config.layout = "application" # Layout for settings page
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
All values shown are defaults and can be omitted.
|
|
91
|
+
|
|
92
|
+
## Light and Dark Mode
|
|
93
|
+
|
|
94
|
+
Users choose Light, Dark or System on the settings page, and the choice is saved
|
|
95
|
+
with their palette. keystone_ui renders each page in, strongest first:
|
|
96
|
+
|
|
97
|
+
1. The choice made with keystone_ui's `ui_theme_toggle` in this browser.
|
|
98
|
+
2. The mode the signed-in user saved.
|
|
99
|
+
3. `config.default_mode`, for users who saved none and for visitors who are not signed in.
|
|
100
|
+
|
|
101
|
+
Saving a mode on the settings page clears the toggle's choice in that browser, so the
|
|
102
|
+
saved mode takes effect. Pages are marked through keystone_ui's
|
|
103
|
+
`keystone_theme_attributes` helper on the layout's `html` tag.
|
|
104
|
+
|
|
105
|
+
## Preset Themes
|
|
106
|
+
|
|
107
|
+
| Name | Accent | Surface | Description |
|
|
108
|
+
|----------|---------|---------|--------------------------------|
|
|
109
|
+
| Default | *(configured)* | *(configured)* | Uses config defaults |
|
|
110
|
+
| Ocean | blue | slate | Cool blues with slate undertones |
|
|
111
|
+
| Forest | emerald | stone | Natural greens with warm stone |
|
|
112
|
+
| Twilight | violet | zinc | Deep violet with clean zinc |
|
|
113
|
+
| Coral | rose | neutral | Warm rose with neutral balance |
|
|
114
|
+
| Arctic | cyan | gray | Bright cyan with crisp gray |
|
|
115
|
+
|
|
116
|
+
## Available Colors
|
|
117
|
+
|
|
118
|
+
**Accents:** blue, emerald, cyan, indigo, violet, rose
|
|
119
|
+
|
|
120
|
+
**Surfaces:** zinc, slate, gray, neutral, stone
|
|
121
|
+
|
|
122
|
+
Each color includes shades 50 through 950 (Tailwind scale). Users can also pick arbitrary hex colors -- the gem generates a full shade palette automatically.
|
|
123
|
+
|
|
124
|
+
## Custom Owner Models
|
|
125
|
+
|
|
126
|
+
The owner association is polymorphic:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
KeystoneUi::Colors.configure do |config|
|
|
130
|
+
config.owner_class_name = "Account"
|
|
131
|
+
config.current_owner_method = :current_account
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Using the CSS Variables
|
|
136
|
+
|
|
137
|
+
```css
|
|
138
|
+
.btn-primary {
|
|
139
|
+
background-color: var(--color-accent-500);
|
|
140
|
+
color: white;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.page-bg {
|
|
144
|
+
background-color: var(--color-surface-50);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.sidebar {
|
|
148
|
+
background-color: var(--color-surface-900);
|
|
149
|
+
color: var(--color-surface-100);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Engine Routes
|
|
154
|
+
|
|
155
|
+
| Method | Path | Action |
|
|
156
|
+
|--------|------|--------|
|
|
157
|
+
| GET | / | Settings page |
|
|
158
|
+
| PATCH | / | Update preference |
|
|
159
|
+
| DELETE | / | Reset to default |
|
|
160
|
+
|
|
161
|
+
## API Reference
|
|
162
|
+
|
|
163
|
+
### `KeystoneUi::Colors::Palettes`
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
KeystoneUi::Colors::Palettes.accent(:blue) # => { 50 => "#eff6ff", ..., 950 => "#172554" }
|
|
167
|
+
KeystoneUi::Colors::Palettes.surface(:zinc) # => { 50 => "#fafafa", ..., 950 => "#09090b" }
|
|
168
|
+
KeystoneUi::Colors::Palettes.generate_shades("#8b5cf6") # => full shade palette from hex
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `KeystoneUi::Colors::Templates`
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
KeystoneUi::Colors::Templates.names # => [:default, :ocean, :forest, :twilight, :coral, :arctic]
|
|
175
|
+
KeystoneUi::Colors::Templates[:ocean] # => { accent: :blue, surface: :slate, label: "Ocean", ... }
|
|
176
|
+
KeystoneUi::Colors::Templates.all # => Hash of all templates
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### `KeystoneUi::Colors::ThemePreference`
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
pref = KeystoneUi::Colors::ThemePreference.find_by(owner: current_user)
|
|
183
|
+
pref.apply_template!(:forest)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Accepts named colors (`"blue"`) or hex values (`"#3b82f6"`) for `accent` and `surface`.
|
|
187
|
+
|
|
188
|
+
## Updating
|
|
189
|
+
|
|
190
|
+
Run the update generator to get the latest Stimulus controller and any new migrations, then migrate:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
bin/rails generate keystone_ui:colors:update
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT License. See [MIT-LICENSE](MIT-LICENSE).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module KeystoneUi
|
|
6
|
+
module Colors
|
|
7
|
+
module CurrentPalette
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
helper_method :keystone_palette_css, :keystone_theme_mode if respond_to?(:helper_method)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set_current_palette
|
|
15
|
+
owner = send(KeystoneUi::Colors.configuration.current_owner_method)
|
|
16
|
+
|
|
17
|
+
unless owner
|
|
18
|
+
@keystone_theme_mode = KeystoneUi::Colors.configuration.default_mode
|
|
19
|
+
build_palette_css(
|
|
20
|
+
KeystoneUi::Colors.configuration.default_accent,
|
|
21
|
+
KeystoneUi::Colors.configuration.default_surface
|
|
22
|
+
)
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
cached = session[:keystone_ui_colors_palette]
|
|
27
|
+
|
|
28
|
+
if cached && !stale_cache?(owner, cached)
|
|
29
|
+
@keystone_theme_mode = cached[:mode] || KeystoneUi::Colors.configuration.default_mode
|
|
30
|
+
build_palette_css(cached[:accent], cached[:surface])
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
preference = KeystoneUi::Colors::ThemePreference.find_by(owner: owner)
|
|
35
|
+
@keystone_theme_mode = preference&.mode || KeystoneUi::Colors.configuration.default_mode
|
|
36
|
+
accent = preference&.accent || KeystoneUi::Colors.configuration.default_accent
|
|
37
|
+
surface = preference&.surface || KeystoneUi::Colors.configuration.default_surface
|
|
38
|
+
|
|
39
|
+
build_palette_css(accent, surface)
|
|
40
|
+
|
|
41
|
+
if preference
|
|
42
|
+
session[:keystone_ui_colors_palette] = {
|
|
43
|
+
accent: preference.accent,
|
|
44
|
+
surface: preference.surface,
|
|
45
|
+
mode: preference.mode,
|
|
46
|
+
updated_at: preference.updated_at.to_i
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def keystone_palette_css
|
|
52
|
+
@keystone_palette_css
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def keystone_theme_mode
|
|
56
|
+
@keystone_theme_mode
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def build_palette_css(accent, surface)
|
|
62
|
+
accent_shades = resolve_shades(accent, :accent)
|
|
63
|
+
surface_shades = resolve_shades(surface, :surface)
|
|
64
|
+
|
|
65
|
+
lines = []
|
|
66
|
+
accent_shades.each { |shade, hex| lines << " --color-accent-#{shade}: #{hex};" }
|
|
67
|
+
surface_shades.each { |shade, hex| lines << " --color-surface-#{shade}: #{hex};" }
|
|
68
|
+
|
|
69
|
+
@keystone_palette_css = ":root {\n#{lines.join("\n")}\n}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def resolve_shades(value, type)
|
|
73
|
+
if value&.start_with?("#")
|
|
74
|
+
KeystoneUi::Colors::Palettes.generate_shades(value)
|
|
75
|
+
else
|
|
76
|
+
(type == :accent) ? KeystoneUi::Colors::Palettes.accent(value) : KeystoneUi::Colors::Palettes.surface(value)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def stale_cache?(owner, cached)
|
|
81
|
+
updated_at = KeystoneUi::Colors::ThemePreference
|
|
82
|
+
.where(owner: owner)
|
|
83
|
+
.pick(:updated_at)
|
|
84
|
+
|
|
85
|
+
return true unless updated_at
|
|
86
|
+
|
|
87
|
+
updated_at.to_i != cached[:updated_at]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
class ApplicationController < ::ApplicationController
|
|
6
|
+
before_action { send(KeystoneUi::Colors.configuration.authentication_method) }
|
|
7
|
+
helper KeystoneUiHelper
|
|
8
|
+
|
|
9
|
+
layout -> { KeystoneUi::Colors.configuration.layout }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
class SettingsController < ApplicationController
|
|
6
|
+
def show
|
|
7
|
+
@preference = theme_preference
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def destroy
|
|
11
|
+
ThemePreference.find_by(owner: current_owner)&.destroy
|
|
12
|
+
redirect_to keystone_ui_colors.settings_path, notice: "Color settings reset to default."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def update
|
|
16
|
+
@preference = theme_preference
|
|
17
|
+
|
|
18
|
+
if preference_params[:template_name].present?
|
|
19
|
+
template = Templates[preference_params[:template_name]]
|
|
20
|
+
@preference.assign_attributes(
|
|
21
|
+
accent: template[:accent].to_s,
|
|
22
|
+
surface: template[:surface].to_s,
|
|
23
|
+
template_name: preference_params[:template_name],
|
|
24
|
+
mode: preference_params[:mode]
|
|
25
|
+
)
|
|
26
|
+
else
|
|
27
|
+
@preference.assign_attributes(preference_params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if @preference.save
|
|
31
|
+
cookies.delete(KeystoneUi::ThemeChoice::COOKIE)
|
|
32
|
+
redirect_to keystone_ui_colors.settings_path, notice: "Color settings updated."
|
|
33
|
+
else
|
|
34
|
+
render :show, status: :unprocessable_entity
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def current_owner
|
|
41
|
+
send(KeystoneUi::Colors.configuration.current_owner_method)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def theme_preference
|
|
45
|
+
ThemePreference.find_or_initialize_by(owner: current_owner)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def preference_params
|
|
49
|
+
params.require(:theme_preference).permit(:accent, :surface, :template_name, :mode)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["accentPicker", "surfacePicker", "customRadio", "templateLabel"]
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
this.accentPickerTarget.addEventListener("change", () => this.customColorChanged())
|
|
8
|
+
this.surfacePickerTarget.addEventListener("change", () => this.customColorChanged())
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
selectTemplate(event) {
|
|
12
|
+
const input = event.currentTarget
|
|
13
|
+
const label = input.closest("[data-template]")
|
|
14
|
+
const accentHex = input.dataset.accentHex
|
|
15
|
+
const surfaceHex = input.dataset.surfaceHex
|
|
16
|
+
|
|
17
|
+
this.setPickerValue(this.accentPickerTarget, accentHex)
|
|
18
|
+
this.setPickerValue(this.surfacePickerTarget, surfaceHex)
|
|
19
|
+
|
|
20
|
+
this.highlightTemplate(label.dataset.template)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
customColorChanged() {
|
|
24
|
+
if (this.hasCustomRadioTarget) this.customRadioTarget.checked = true
|
|
25
|
+
this.highlightTemplate("custom")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
highlightTemplate(selected) {
|
|
29
|
+
this.templateLabelTargets.forEach(label => {
|
|
30
|
+
const border = label.querySelector("span")
|
|
31
|
+
if (label.dataset.template === selected) {
|
|
32
|
+
border.classList.remove("border-transparent")
|
|
33
|
+
border.classList.add("border-accent-500")
|
|
34
|
+
} else {
|
|
35
|
+
border.classList.remove("border-accent-500")
|
|
36
|
+
border.classList.add("border-transparent")
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setPickerValue(wrapper, hex) {
|
|
42
|
+
const input = wrapper.querySelector("input[type='hidden']")
|
|
43
|
+
const swatch = wrapper.querySelector("[data-color-picker-target='swatch']")
|
|
44
|
+
const hexLabel = wrapper.querySelector("[data-color-picker-target='hexLabel']")
|
|
45
|
+
|
|
46
|
+
if (input) input.value = hex
|
|
47
|
+
if (swatch) swatch.style.backgroundColor = hex
|
|
48
|
+
if (hexLabel) hexLabel.textContent = hex
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
class ThemePreference < ApplicationRecord
|
|
6
|
+
self.table_name = "keystone_ui_colors_theme_preferences"
|
|
7
|
+
|
|
8
|
+
belongs_to :owner, polymorphic: true
|
|
9
|
+
|
|
10
|
+
SUPPORTED_ACCENTS = %w[blue emerald cyan indigo violet rose].freeze
|
|
11
|
+
|
|
12
|
+
SUPPORTED_SURFACES = %w[zinc slate gray neutral stone].freeze
|
|
13
|
+
|
|
14
|
+
HEX_COLOR = /\A#[0-9a-fA-F]{6}\z/
|
|
15
|
+
|
|
16
|
+
MODES = %w[light dark system].freeze
|
|
17
|
+
|
|
18
|
+
validate :accent_is_valid
|
|
19
|
+
validate :surface_is_valid
|
|
20
|
+
validates :template_name, inclusion: { in: Templates.names.map(&:to_s) }, allow_blank: true
|
|
21
|
+
validates :mode, inclusion: { in: MODES }, allow_nil: true
|
|
22
|
+
|
|
23
|
+
def apply_template!(name)
|
|
24
|
+
template = Templates[name]
|
|
25
|
+
update!(
|
|
26
|
+
accent: template[:accent].to_s,
|
|
27
|
+
surface: template[:surface].to_s,
|
|
28
|
+
template_name: name.to_s
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def accent_is_valid
|
|
35
|
+
return if SUPPORTED_ACCENTS.include?(accent) || accent&.match?(HEX_COLOR)
|
|
36
|
+
|
|
37
|
+
errors.add(:accent, "must be a supported color name or hex value")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def surface_is_valid
|
|
41
|
+
return if SUPPORTED_SURFACES.include?(surface) || surface&.match?(HEX_COLOR)
|
|
42
|
+
|
|
43
|
+
errors.add(:surface, "must be a supported color name or hex value")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<%= ui_page_header(title: "Color Settings") %>
|
|
2
|
+
|
|
3
|
+
<% if flash[:notice] %>
|
|
4
|
+
<%= ui_alert(message: flash[:notice], type: :success) %>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<%
|
|
8
|
+
current_accent_hex = if @preference.accent&.start_with?("#")
|
|
9
|
+
@preference.accent
|
|
10
|
+
else
|
|
11
|
+
KeystoneUi::Colors::Palettes.accent(@preference.accent || "blue")[500]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
current_surface_hex = if @preference.surface&.start_with?("#")
|
|
15
|
+
@preference.surface
|
|
16
|
+
else
|
|
17
|
+
KeystoneUi::Colors::Palettes.surface(@preference.surface || "zinc")[500]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
selected_template = @preference.template_name
|
|
21
|
+
custom_selected = @preference.accent&.start_with?("#") || (selected_template.blank? && @preference.persisted?)
|
|
22
|
+
%>
|
|
23
|
+
|
|
24
|
+
<%= ui_panel(padding: :lg) do %>
|
|
25
|
+
<%= form_with(
|
|
26
|
+
url: keystone_ui_colors.settings_path,
|
|
27
|
+
method: :patch,
|
|
28
|
+
scope: :theme_preference,
|
|
29
|
+
data: { turbo: false, controller: "keystone-ui--colors--theme-settings", selected_template: selected_template }
|
|
30
|
+
) do |form| %>
|
|
31
|
+
<div style="display: flex; flex-direction: column; gap: 2rem;">
|
|
32
|
+
<%= ui_section(title: "Theme", spacing: :sm) do %>
|
|
33
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
|
34
|
+
<% KeystoneUi::Colors::Templates.names.each do |name| %>
|
|
35
|
+
<% template = KeystoneUi::Colors::Templates[name] %>
|
|
36
|
+
<% accent_hex = KeystoneUi::Colors::Palettes.accent(template[:accent])[500] %>
|
|
37
|
+
<% surface_hex = KeystoneUi::Colors::Palettes.surface(template[:surface])[500] %>
|
|
38
|
+
<%= ui_option_card(
|
|
39
|
+
name: "theme_preference[template_name]",
|
|
40
|
+
value: name,
|
|
41
|
+
selected: selected_template == name.to_s,
|
|
42
|
+
input_data: {
|
|
43
|
+
action: "keystone-ui--colors--theme-settings#selectTemplate",
|
|
44
|
+
"accent-hex": accent_hex,
|
|
45
|
+
"surface-hex": surface_hex
|
|
46
|
+
},
|
|
47
|
+
label_data: {
|
|
48
|
+
"keystone-ui--colors--theme-settings-target": "templateLabel",
|
|
49
|
+
template: name
|
|
50
|
+
}
|
|
51
|
+
) do %>
|
|
52
|
+
<span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 9999px; background-color: <%= accent_hex %>;"></span>
|
|
53
|
+
<span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 0.25rem; background-color: <%= surface_hex %>;"></span>
|
|
54
|
+
<span><%= template[:label] %></span>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% end %>
|
|
57
|
+
<%= ui_option_card(
|
|
58
|
+
name: "theme_preference[template_name]",
|
|
59
|
+
value: "",
|
|
60
|
+
selected: custom_selected,
|
|
61
|
+
input_data: {
|
|
62
|
+
"keystone-ui--colors--theme-settings-target": "customRadio"
|
|
63
|
+
},
|
|
64
|
+
label_data: {
|
|
65
|
+
"keystone-ui--colors--theme-settings-target": "templateLabel",
|
|
66
|
+
template: "custom"
|
|
67
|
+
}
|
|
68
|
+
) do %>
|
|
69
|
+
<span>Custom</span>
|
|
70
|
+
<% end %>
|
|
71
|
+
</div>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<%= ui_section(title: "Accent Color", spacing: :sm) do %>
|
|
75
|
+
<div data-keystone-ui--colors--theme-settings-target="accentPicker">
|
|
76
|
+
<%= ui_color_picker(name: "theme_preference[accent]", value: current_accent_hex) %>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<%= ui_section(title: "Surface", spacing: :sm) do %>
|
|
81
|
+
<div data-keystone-ui--colors--theme-settings-target="surfacePicker">
|
|
82
|
+
<%= ui_color_picker(name: "theme_preference[surface]", value: current_surface_hex) %>
|
|
83
|
+
</div>
|
|
84
|
+
<% end %>
|
|
85
|
+
|
|
86
|
+
<%= ui_section(title: "Mode", spacing: :sm) do %>
|
|
87
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
|
88
|
+
<% KeystoneUi::Colors::ThemePreference::MODES.each do |mode| %>
|
|
89
|
+
<%= ui_radio_card(name: "theme_preference[mode]", value: mode, label: mode.capitalize, checked: (@preference.mode || KeystoneUi::Colors.configuration.default_mode) == mode) %>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
93
|
+
|
|
94
|
+
<div>
|
|
95
|
+
<%= ui_button(label: "Save", type: :submit) %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<% end %>
|
|
99
|
+
<% end %>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module KeystoneUi
|
|
7
|
+
module Colors
|
|
8
|
+
module Generators
|
|
9
|
+
class InstallGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
|
13
|
+
|
|
14
|
+
desc "Installs KeystoneUi::Colors: copies migration and prints setup instructions."
|
|
15
|
+
|
|
16
|
+
def copy_migration
|
|
17
|
+
migration_template(
|
|
18
|
+
"create_keystone_ui_colors_theme_preferences.rb.erb",
|
|
19
|
+
"db/migrate/create_keystone_ui_colors_theme_preferences.rb"
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def copy_stimulus_controller
|
|
24
|
+
js_source = File.expand_path("../../../../../app/javascript/keystone_ui/colors/theme_settings_controller.js", __dir__)
|
|
25
|
+
create_file "app/javascript/controllers/keystone_ui/colors/theme_settings_controller.js", File.read(js_source)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def show_instructions
|
|
29
|
+
say ""
|
|
30
|
+
say "KeystoneUi::Colors installed! Next steps:", :green
|
|
31
|
+
say ""
|
|
32
|
+
say " 1. Run migrations:"
|
|
33
|
+
say " rails db:migrate"
|
|
34
|
+
say ""
|
|
35
|
+
say " 2. Mount the engine in config/routes.rb:"
|
|
36
|
+
say " mount KeystoneUi::Colors::Engine => '/keystone_ui_colors'"
|
|
37
|
+
say ""
|
|
38
|
+
say " 3. Include the concern in your ApplicationController:"
|
|
39
|
+
say " include KeystoneUi::Colors::CurrentPalette"
|
|
40
|
+
say " before_action :set_current_palette"
|
|
41
|
+
say ""
|
|
42
|
+
say " 4. Add the style tag to your layout <head>:"
|
|
43
|
+
say " <%= keystone_palette_style_tag %>"
|
|
44
|
+
say ""
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateKeystoneUiColorsThemePreferences < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :keystone_ui_colors_theme_preferences do |t|
|
|
4
|
+
t.string :accent, null: false
|
|
5
|
+
t.string :surface, null: false
|
|
6
|
+
t.string :template_name
|
|
7
|
+
t.string :mode
|
|
8
|
+
t.references :owner, polymorphic: true, null: false
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :keystone_ui_colors_theme_preferences, %i[owner_type owner_id], unique: true,
|
|
13
|
+
name: "index_keystone_ui_colors_theme_prefs_on_owner"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module KeystoneUi
|
|
7
|
+
module Colors
|
|
8
|
+
module Generators
|
|
9
|
+
class UpdateGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("../../../../../app/javascript/keystone_ui/colors", __dir__)
|
|
13
|
+
|
|
14
|
+
desc "Updates KeystoneUi::Colors: copies the Stimulus controller and adds new migrations."
|
|
15
|
+
|
|
16
|
+
def add_mode_migration
|
|
17
|
+
migration_template(
|
|
18
|
+
File.expand_path("templates/add_mode_to_keystone_ui_colors_theme_preferences.rb.erb", __dir__),
|
|
19
|
+
"db/migrate/add_mode_to_keystone_ui_colors_theme_preferences.rb"
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def copy_stimulus_controller
|
|
24
|
+
copy_file "theme_settings_controller.js",
|
|
25
|
+
"app/javascript/controllers/keystone_ui/colors/theme_settings_controller.js"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :current_owner_method, :authentication_method, :default_template, :default_accent, :default_surface, :default_mode, :layout
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@current_owner_method = :current_user
|
|
10
|
+
@authentication_method = :authenticate_user!
|
|
11
|
+
@default_template = :ocean
|
|
12
|
+
@default_accent = "blue"
|
|
13
|
+
@default_surface = "zinc"
|
|
14
|
+
@default_mode = "light"
|
|
15
|
+
@layout = "application"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.configuration
|
|
20
|
+
@configuration ||= Configuration.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.configure
|
|
24
|
+
yield(configuration)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.reset_configuration!
|
|
28
|
+
@configuration = Configuration.new
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
isolate_namespace KeystoneUi::Colors
|
|
7
|
+
|
|
8
|
+
initializer "keystone_ui.colors.theme_mode" do
|
|
9
|
+
KeystoneUi.configure do |config|
|
|
10
|
+
config.theme_mode_supplier = lambda do |view|
|
|
11
|
+
view.respond_to?(:keystone_theme_mode) ? view.keystone_theme_mode : KeystoneUi::Colors.configuration.default_mode
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "keystone_ui.colors.url_helpers" do
|
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
|
18
|
+
helper Rails.application.routes.url_helpers
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
module Palettes
|
|
6
|
+
ACCENTS = {
|
|
7
|
+
blue: { 50 => "#eff6ff", 100 => "#dbeafe", 200 => "#bfdbfe", 300 => "#93c5fd", 400 => "#60a5fa", 500 => "#3b82f6", 600 => "#2563eb", 700 => "#1d4ed8", 800 => "#1e40af", 900 => "#1e3a8a", 950 => "#172554" },
|
|
8
|
+
emerald: { 50 => "#ecfdf5", 100 => "#d1fae5", 200 => "#a7f3d0", 300 => "#6ee7b7", 400 => "#34d399", 500 => "#10b981", 600 => "#059669", 700 => "#047857", 800 => "#065f46", 900 => "#064e3b", 950 => "#022c22" },
|
|
9
|
+
cyan: { 50 => "#ecfeff", 100 => "#cffafe", 200 => "#a5f3fc", 300 => "#67e8f9", 400 => "#22d3ee", 500 => "#06b6d4", 600 => "#0891b2", 700 => "#0e7490", 800 => "#155e75", 900 => "#164e63", 950 => "#083344" },
|
|
10
|
+
indigo: { 50 => "#eef2ff", 100 => "#e0e7ff", 200 => "#c7d2fe", 300 => "#a5b4fc", 400 => "#818cf8", 500 => "#6366f1", 600 => "#4f46e5", 700 => "#4338ca", 800 => "#3730a3", 900 => "#312e81", 950 => "#1e1b4b" },
|
|
11
|
+
violet: { 50 => "#f5f3ff", 100 => "#ede9fe", 200 => "#ddd6fe", 300 => "#c4b5fd", 400 => "#a78bfa", 500 => "#8b5cf6", 600 => "#7c3aed", 700 => "#6d28d9", 800 => "#5b21b6", 900 => "#4c1d95", 950 => "#2e1065" },
|
|
12
|
+
rose: { 50 => "#fff1f2", 100 => "#ffe4e6", 200 => "#fecdd3", 300 => "#fda4af", 400 => "#fb7185", 500 => "#f43f5e", 600 => "#e11d48", 700 => "#be123c", 800 => "#9f1239", 900 => "#881337", 950 => "#4c0519" }
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
SURFACES = {
|
|
16
|
+
zinc: { 50 => "#fafafa", 100 => "#f4f4f5", 200 => "#e4e4e7", 300 => "#d4d4d8", 400 => "#a1a1aa", 500 => "#71717a", 600 => "#52525b", 700 => "#3f3f46", 800 => "#27272a", 900 => "#18181b", 950 => "#09090b" },
|
|
17
|
+
slate: { 50 => "#f8fafc", 100 => "#f1f5f9", 200 => "#e2e8f0", 300 => "#cbd5e1", 400 => "#94a3b8", 500 => "#64748b", 600 => "#475569", 700 => "#334155", 800 => "#1e293b", 900 => "#0f172a", 950 => "#020617" },
|
|
18
|
+
gray: { 50 => "#f9fafb", 100 => "#f3f4f6", 200 => "#e5e7eb", 300 => "#d1d5db", 400 => "#9ca3af", 500 => "#6b7280", 600 => "#4b5563", 700 => "#374151", 800 => "#1f2937", 900 => "#111827", 950 => "#030712" },
|
|
19
|
+
neutral: { 50 => "#fafafa", 100 => "#f5f5f5", 200 => "#e5e5e5", 300 => "#d4d4d4", 400 => "#a3a3a3", 500 => "#737373", 600 => "#525252", 700 => "#404040", 800 => "#262626", 900 => "#171717", 950 => "#0a0a0a" },
|
|
20
|
+
stone: { 50 => "#fafaf9", 100 => "#f5f5f4", 200 => "#e7e5e4", 300 => "#d6d3d1", 400 => "#a8a29e", 500 => "#78716c", 600 => "#57534e", 700 => "#44403c", 800 => "#292524", 900 => "#1c1917", 950 => "#0c0a09" }
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
# Shade lightness targets (0.0 = black, 1.0 = white)
|
|
24
|
+
SHADE_TARGETS = {
|
|
25
|
+
50 => 0.95, 100 => 0.9, 200 => 0.8, 300 => 0.7, 400 => 0.6,
|
|
26
|
+
500 => 0.0, 600 => -0.15, 700 => -0.3, 800 => -0.45, 900 => -0.6, 950 => -0.75
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def self.accent(name)
|
|
30
|
+
ACCENTS.fetch(name.to_sym)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.surface(name)
|
|
34
|
+
SURFACES.fetch(name.to_sym)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.generate_shades(hex)
|
|
38
|
+
r, g, b = hex_to_rgb(hex)
|
|
39
|
+
|
|
40
|
+
SHADE_TARGETS.each_with_object({}) do |(shade, factor), result|
|
|
41
|
+
result[shade] = if shade == 500
|
|
42
|
+
hex
|
|
43
|
+
elsif factor > 0
|
|
44
|
+
rgb_to_hex(mix(r, g, b, 255, 255, 255, factor))
|
|
45
|
+
else
|
|
46
|
+
rgb_to_hex(mix(r, g, b, 0, 0, 0, factor.abs))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.hex_to_rgb(hex)
|
|
52
|
+
hex = hex.delete_prefix("#")
|
|
53
|
+
[ hex[0..1], hex[2..3], hex[4..5] ].map { |c| c.to_i(16) }
|
|
54
|
+
end
|
|
55
|
+
private_class_method :hex_to_rgb
|
|
56
|
+
|
|
57
|
+
def self.rgb_to_hex(rgb)
|
|
58
|
+
"#" + rgb.map { |c| c.clamp(0, 255).round.to_s(16).rjust(2, "0") }.join
|
|
59
|
+
end
|
|
60
|
+
private_class_method :rgb_to_hex
|
|
61
|
+
|
|
62
|
+
def self.mix(r1, g1, b1, r2, g2, b2, weight)
|
|
63
|
+
[
|
|
64
|
+
r1 + (r2 - r1) * weight,
|
|
65
|
+
g1 + (g2 - g1) * weight,
|
|
66
|
+
b1 + (b2 - b1) * weight
|
|
67
|
+
]
|
|
68
|
+
end
|
|
69
|
+
private_class_method :mix
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KeystoneUi
|
|
4
|
+
module Colors
|
|
5
|
+
module Templates
|
|
6
|
+
PRESETS = {
|
|
7
|
+
ocean: {
|
|
8
|
+
accent: :blue,
|
|
9
|
+
surface: :slate,
|
|
10
|
+
label: "Ocean",
|
|
11
|
+
description: "Cool blues with slate undertones"
|
|
12
|
+
},
|
|
13
|
+
forest: {
|
|
14
|
+
accent: :emerald,
|
|
15
|
+
surface: :stone,
|
|
16
|
+
label: "Forest",
|
|
17
|
+
description: "Natural greens with warm stone"
|
|
18
|
+
},
|
|
19
|
+
twilight: {
|
|
20
|
+
accent: :violet,
|
|
21
|
+
surface: :zinc,
|
|
22
|
+
label: "Twilight",
|
|
23
|
+
description: "Deep violet with clean zinc"
|
|
24
|
+
},
|
|
25
|
+
coral: {
|
|
26
|
+
accent: :rose,
|
|
27
|
+
surface: :neutral,
|
|
28
|
+
label: "Coral",
|
|
29
|
+
description: "Warm rose with neutral balance"
|
|
30
|
+
},
|
|
31
|
+
arctic: {
|
|
32
|
+
accent: :cyan,
|
|
33
|
+
surface: :gray,
|
|
34
|
+
label: "Arctic",
|
|
35
|
+
description: "Bright cyan with crisp gray"
|
|
36
|
+
}
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
def self.all
|
|
40
|
+
PRESETS.merge(default: default_template)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.[](name)
|
|
44
|
+
return default_template if name.to_sym == :default
|
|
45
|
+
|
|
46
|
+
PRESETS.fetch(name.to_sym)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.names
|
|
50
|
+
[ :default ] + PRESETS.keys
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.default_template
|
|
54
|
+
{
|
|
55
|
+
accent: KeystoneUi::Colors.configuration.default_accent,
|
|
56
|
+
surface: KeystoneUi::Colors.configuration.default_surface,
|
|
57
|
+
label: "Default",
|
|
58
|
+
description: "Default theme"
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require "keystone_ui"
|
|
2
|
+
require "keystone_ui/colors/version"
|
|
3
|
+
require "keystone_ui/colors/configuration"
|
|
4
|
+
require "keystone_ui/colors/templates"
|
|
5
|
+
require "keystone_ui/colors/palettes"
|
|
6
|
+
require "keystone_ui/colors/engine"
|
|
7
|
+
|
|
8
|
+
module KeystoneUi
|
|
9
|
+
module Colors
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "keystone_ui/colors"
|
metadata
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: keystone_ui-colors
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tyler Schneider
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: keystone_ui
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.10.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.10.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: railties
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activerecord
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '7.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '7.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: activesupport
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '7.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '7.0'
|
|
69
|
+
description: A companion engine for keystone_ui that provides preset themes and per-user
|
|
70
|
+
color palette persistence.
|
|
71
|
+
email:
|
|
72
|
+
- tylercschneider@gmail.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- MIT-LICENSE
|
|
78
|
+
- README.md
|
|
79
|
+
- Rakefile
|
|
80
|
+
- app/controllers/concerns/keystone_ui/colors/current_palette.rb
|
|
81
|
+
- app/controllers/keystone_ui/colors/application_controller.rb
|
|
82
|
+
- app/controllers/keystone_ui/colors/settings_controller.rb
|
|
83
|
+
- app/helpers/keystone_ui/colors/palette_helper.rb
|
|
84
|
+
- app/javascript/keystone_ui/colors/theme_settings_controller.js
|
|
85
|
+
- app/models/keystone_ui/colors/application_record.rb
|
|
86
|
+
- app/models/keystone_ui/colors/theme_preference.rb
|
|
87
|
+
- app/views/keystone_ui/colors/settings/show.html.erb
|
|
88
|
+
- config/routes.rb
|
|
89
|
+
- lib/generators/keystone_ui/colors/install/install_generator.rb
|
|
90
|
+
- lib/generators/keystone_ui/colors/install/templates/create_keystone_ui_colors_theme_preferences.rb.erb
|
|
91
|
+
- lib/generators/keystone_ui/colors/update/templates/add_mode_to_keystone_ui_colors_theme_preferences.rb.erb
|
|
92
|
+
- lib/generators/keystone_ui/colors/update/update_generator.rb
|
|
93
|
+
- lib/keystone_ui-colors.rb
|
|
94
|
+
- lib/keystone_ui/colors.rb
|
|
95
|
+
- lib/keystone_ui/colors/configuration.rb
|
|
96
|
+
- lib/keystone_ui/colors/engine.rb
|
|
97
|
+
- lib/keystone_ui/colors/palettes.rb
|
|
98
|
+
- lib/keystone_ui/colors/templates.rb
|
|
99
|
+
- lib/keystone_ui/colors/version.rb
|
|
100
|
+
homepage: https://github.com/DYB-Development/keystone_ui-colors
|
|
101
|
+
licenses:
|
|
102
|
+
- MIT
|
|
103
|
+
metadata:
|
|
104
|
+
homepage_uri: https://github.com/DYB-Development/keystone_ui-colors
|
|
105
|
+
source_code_uri: https://github.com/DYB-Development/keystone_ui-colors
|
|
106
|
+
changelog_uri: https://github.com/DYB-Development/keystone_ui-colors/blob/main/CHANGELOG.md
|
|
107
|
+
bug_tracker_uri: https://github.com/DYB-Development/keystone_ui-colors/issues
|
|
108
|
+
documentation_uri: https://github.com/DYB-Development/keystone_ui-colors#readme
|
|
109
|
+
rubygems_mfa_required: 'true'
|
|
110
|
+
post_install_message:
|
|
111
|
+
rdoc_options: []
|
|
112
|
+
require_paths:
|
|
113
|
+
- lib
|
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: 3.1.0
|
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
requirements: []
|
|
125
|
+
rubygems_version: 3.5.16
|
|
126
|
+
signing_key:
|
|
127
|
+
specification_version: 4
|
|
128
|
+
summary: Per-user color palette settings for Keystone UI.
|
|
129
|
+
test_files: []
|