keystone_ui-colors 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5224ad9955865982d987f3916ba22c431da0a8119ca42df9117f37374fc88b6
4
- data.tar.gz: d0bcd8a902a86d5410943af32b8ad41319af8b3adb30c5ebfeae6dbfdb1e8194
3
+ metadata.gz: 0be1b3b828200e62a25182fce5604530b562f4b9223163c2ff72ec9956165a58
4
+ data.tar.gz: f17e542ec6c0edaf81af9550f500422ad98735c4e83070810b0664ecd65f5704
5
5
  SHA512:
6
- metadata.gz: 1e83b526543374f4cce58b86a0aace773fba8205fea2f2de28a2bf87925d5c64299055e8d71fab02ab1d80447b557ce75dd730c01403812e7e2a67155868adb0
7
- data.tar.gz: '080fc6a5bafcc87e2d3555db311698d234fc5f13d623b9792999a4fd7a0129462ed0b1ae826973a0ab0cd53b4c48e92cd72fa1c0c77ab2a5eb0022294deec8f7'
6
+ metadata.gz: 82da08d19ce27f46e0197a9a3d4cd7ca9b121530ecd564a52e79968e0145e08d733f29e1deaf9caf998e02d009addbc84b5d1b7090830d291596a132c575e9df
7
+ data.tar.gz: f5658c136c63b0134a25d867c90071b3c103ae35d59e36160d7004d1ca693958232b5f3e20892e207b60636f02a7043f86a65fc25fb0758d89875fa892d57af4
data/README.md CHANGED
@@ -85,6 +85,8 @@ KeystoneUi::Colors.configure do |config|
85
85
  config.default_mode = "light" # Fallback theme mode: "light", "dark", "system" or "custom"
86
86
  config.default_background = "#ffffff" # Custom mode background when nothing else sets one
87
87
  config.default_text = "#18181b" # Custom mode text colour when nothing else sets one
88
+ config.account_colors = true # Let account owners choose their account's colours
89
+ config.current_account_method = nil # Controller method for the current account, such as :current_account
88
90
  config.layout = "application" # Layout for settings page
89
91
  end
90
92
  ```
@@ -117,6 +119,44 @@ gray and zinc shade as a blend of the text into it.
117
119
  - Anything not set falls back to `config.default_background` and
118
120
  `config.default_text`.
119
121
 
122
+ ## Account Colours
123
+
124
+ Colours are chosen at up to three levels, and a page uses the first that applies:
125
+
126
+ 1. The signed-in user's own colours, when the app lets accounts choose and the
127
+ user's account lets its members choose.
128
+ 2. The account's colours, when the app lets accounts choose.
129
+ 3. The app's configured defaults, which fall back to keystone_ui's own colours.
130
+
131
+ An account's colours are a `ThemePreference` owned by the account. Its
132
+ `members_choose` column, `true` by default, decides whether members may use their
133
+ own colours. Set `config.current_account_method` to the controller method that
134
+ returns the current account. With none set, the account level is skipped.
135
+
136
+ Light, Dark and System stay each user's own choice at every level. Custom is
137
+ offered to a user who may choose their own colours, and to anyone whose applying
138
+ colours draw a background other than white.
139
+
140
+ Two partials place the pickers in an app's own settings, each taking `person:`,
141
+ `account:` and `submit_url:`:
142
+
143
+ - `keystone_ui/colors/settings/picker` saves through `KeystoneUi::Colors::PickColours`.
144
+ A user who may not choose colours sees only the mode, and a save from them keeps
145
+ only the mode.
146
+ - `keystone_ui/colors/settings/account_picker` saves through
147
+ `KeystoneUi::Colors::PickAccountColours`, and holds the account's colours and
148
+ its Members switch.
149
+
150
+ Who may set an account's colours is for the app to decide. With settings_hub,
151
+ register the account picker in the account area behind a capability:
152
+
153
+ ```ruby
154
+ SettingsHub.section :account_appearance, area: :account, title: "Appearance",
155
+ capability: :manage_account,
156
+ renders: "keystone_ui/colors/settings/account_picker",
157
+ runs: "KeystoneUi::Colors::PickAccountColours"
158
+ ```
159
+
120
160
  ## Keeping a Page on the Host's Colours
121
161
 
122
162
  A controller that declares `keystone_host_colors` renders its pages in the host's
@@ -21,6 +21,8 @@ module KeystoneUi
21
21
  owner = send(KeystoneUi::Colors.configuration.current_owner_method)
22
22
 
23
23
  return apply_host_palette unless owner
24
+ account = keystone_account
25
+ return apply_chosen_palette(owner, account) if account || !KeystoneUi::Colors.configuration.account_colors
24
26
 
25
27
  cached = session[:keystone_ui_colors_palette]
26
28
 
@@ -54,6 +56,12 @@ module KeystoneUi
54
56
  end
55
57
  end
56
58
 
59
+ def apply_chosen_palette(owner, account)
60
+ choice = KeystoneUi::Colors::ColourChoice.new(person: owner, account: account)
61
+ @keystone_theme_mode = choice.mode
62
+ write_palette(choice.applying_preference)
63
+ end
64
+
57
65
  def apply_host_palette
58
66
  @keystone_theme_mode = KeystoneUi::Colors.configuration.default_mode
59
67
  build_palette_css(
@@ -73,6 +81,20 @@ module KeystoneUi
73
81
 
74
82
  private
75
83
 
84
+ def keystone_account
85
+ method = KeystoneUi::Colors.configuration.current_account_method
86
+ send(method) if method
87
+ end
88
+
89
+ def write_palette(preference)
90
+ surface = preference&.surface || KeystoneUi::Colors.configuration.default_surface
91
+ build_palette_css(
92
+ preference&.accent || KeystoneUi::Colors.configuration.default_accent,
93
+ surface,
94
+ KeystoneUi::Colors::CustomColours.new(template_name: preference&.template_name, surface: surface, text: preference&.text)
95
+ )
96
+ end
97
+
76
98
  def build_palette_css(accent, surface, custom = nil)
77
99
  accent_shades = resolve_shades(accent, :accent)
78
100
  surface_shades = resolve_shades(surface, :surface)
@@ -4,9 +4,15 @@ export default class extends Controller {
4
4
  static targets = ["accentPicker", "surfacePicker", "textPicker", "customRadio", "templateLabel"]
5
5
 
6
6
  connect() {
7
- this.accentPickerTarget.addEventListener("change", () => this.customColorChanged())
8
- this.surfacePickerTarget.addEventListener("change", () => this.customColorChanged())
9
- this.textPickerTarget.addEventListener("change", () => this.customColorChanged())
7
+ this.colorPickerTargets().forEach(picker => picker.addEventListener("change", () => this.customColorChanged()))
8
+ }
9
+
10
+ colorPickerTargets() {
11
+ return [
12
+ this.hasAccentPickerTarget && this.accentPickerTarget,
13
+ this.hasSurfacePickerTarget && this.surfacePickerTarget,
14
+ this.hasTextPickerTarget && this.textPickerTarget
15
+ ].filter(Boolean)
10
16
  }
11
17
 
12
18
  selectTemplate(event) {
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ module Colors
5
+ class ColourChoice
6
+ WHITE = "#ffffff"
7
+
8
+ def initialize(person:, account:)
9
+ @person = person
10
+ @account = account
11
+ end
12
+
13
+ def person_chooses?
14
+ return false unless KeystoneUi::Colors.configuration.account_colors
15
+
16
+ account_preference.nil? || account_preference.members_choose
17
+ end
18
+
19
+ def applying_preference
20
+ return nil unless KeystoneUi::Colors.configuration.account_colors
21
+ return own_preference || account_preference if person_chooses?
22
+
23
+ account_preference
24
+ end
25
+
26
+ def mode
27
+ own_preference&.mode || KeystoneUi::Colors.configuration.default_mode
28
+ end
29
+
30
+ def custom_offered?
31
+ preference = applying_preference
32
+ background = CustomColours.new(template_name: preference&.template_name, surface: preference&.surface, text: preference&.text).background
33
+ !background.casecmp?(WHITE)
34
+ end
35
+
36
+ private
37
+
38
+ def own_preference
39
+ @own_preference ||= ThemePreference.find_by(owner: @person)
40
+ end
41
+
42
+ def account_preference
43
+ return nil unless @account
44
+
45
+ @account_preference ||= ThemePreference.find_by(owner: @account)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KeystoneUi
4
+ module Colors
5
+ class PickAccountColours
6
+ def initialize(values:, person: nil, account: nil)
7
+ @account = account
8
+ @values = values
9
+ end
10
+
11
+ def call
12
+ return Refusal.new("This app does not let accounts choose their colours.") unless KeystoneUi::Colors.configuration.account_colors
13
+
14
+ result = PickColours.new(owner: @account, values: @values.except(:members_choose, :mode)).call
15
+ return result unless result.ok? && @values.key?(:members_choose)
16
+
17
+ ThemePreference.find_by(owner: @account).update!(members_choose: @values[:members_choose] == "1")
18
+ result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -23,8 +23,21 @@ module KeystoneUi
23
23
  @preference ||= ThemePreference.find_or_initialize_by(owner: @owner)
24
24
  end
25
25
 
26
+ def mode_only
27
+ defaults = preference.new_record? ? { accent: KeystoneUi::Colors.configuration.default_accent, surface: KeystoneUi::Colors.configuration.default_surface } : {}
28
+ defaults.merge(mode: @values[:mode]).compact
29
+ end
30
+
31
+ def custom_colours
32
+ colours = { accent: @values[:accent], surface: @values[:surface], text: @values[:text], mode: @values[:mode] }.compact
33
+ colours[:template_name] = nil if @values.key?(:template_name)
34
+ colours
35
+ end
36
+
26
37
  def chosen_colours
27
- return { accent: @values[:accent], surface: @values[:surface], text: @values[:text], mode: @values[:mode] }.compact if @values[:template_name].blank?
38
+ return mode_only unless ColourChoice.new(person: @owner, account: @account).person_chooses?
39
+
40
+ return custom_colours if @values[:template_name].blank?
28
41
 
29
42
  template = Templates[@values[:template_name]]
30
43
  {
@@ -0,0 +1,22 @@
1
+ <% preference = KeystoneUi::Colors::ThemePreference.find_or_initialize_by(owner: account) %>
2
+
3
+ <%= ui_panel(padding: :lg) do %>
4
+ <%= form_with(
5
+ url: submit_url,
6
+ method: :patch,
7
+ data: { turbo: false, controller: "keystone-ui--colors--theme-settings", selected_template: preference.template_name }
8
+ ) do |form| %>
9
+ <div style="display: flex; flex-direction: column; gap: 2rem;">
10
+ <%= render partial: "keystone_ui/colors/settings/colours", locals: { preference: preference } %>
11
+
12
+ <%= ui_section(title: "Members", spacing: :sm) do %>
13
+ <%= hidden_field_tag "members_choose", "0", id: nil %>
14
+ <%= ui_checkbox_row(name: "members_choose", value: "1", label: "Members choose their own colours", checked: preference.members_choose) %>
15
+ <% end %>
16
+
17
+ <div>
18
+ <%= ui_button(label: "Save", type: :submit) %>
19
+ </div>
20
+ </div>
21
+ <% end %>
22
+ <% end %>
@@ -0,0 +1,83 @@
1
+ <%
2
+ current_accent_hex = if preference.accent&.start_with?("#")
3
+ preference.accent
4
+ else
5
+ KeystoneUi::Colors::Palettes.accent(preference.accent || "blue")[500]
6
+ end
7
+
8
+ current_surface_hex = if preference.surface&.start_with?("#")
9
+ preference.surface
10
+ else
11
+ KeystoneUi::Colors::Palettes.surface(preference.surface || "zinc")[500]
12
+ end
13
+
14
+ current_text_hex = KeystoneUi::Colors::CustomColours.new(
15
+ template_name: preference.template_name,
16
+ surface: preference.surface,
17
+ text: preference.text
18
+ ).text
19
+
20
+ selected_template = preference.template_name
21
+ custom_selected = preference.accent&.start_with?("#") || (selected_template.blank? && preference.persisted?)
22
+ %>
23
+
24
+ <%= ui_section(title: "Theme", spacing: :sm) do %>
25
+ <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
26
+ <% KeystoneUi::Colors::Templates.names.each do |name| %>
27
+ <% template = KeystoneUi::Colors::Templates[name] %>
28
+ <% accent_hex = KeystoneUi::Colors::Palettes.accent(template[:accent])[500] %>
29
+ <% surface_hex = KeystoneUi::Colors::Palettes.surface(template[:surface])[500] %>
30
+ <%= ui_option_card(
31
+ name: "template_name",
32
+ value: name,
33
+ selected: selected_template == name.to_s,
34
+ input_data: {
35
+ action: "keystone-ui--colors--theme-settings#selectTemplate",
36
+ "accent-hex": accent_hex,
37
+ "surface-hex": surface_hex,
38
+ "text-hex": template[:text]
39
+ },
40
+ label_data: {
41
+ "keystone-ui--colors--theme-settings-target": "templateLabel",
42
+ template: name
43
+ }
44
+ ) do %>
45
+ <span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 9999px; background-color: <%= accent_hex %>;"></span>
46
+ <span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 0.25rem; background-color: <%= surface_hex %>;"></span>
47
+ <span><%= template[:label] %></span>
48
+ <% end %>
49
+ <% end %>
50
+ <%= ui_option_card(
51
+ name: "template_name",
52
+ value: "",
53
+ selected: custom_selected,
54
+ input_data: {
55
+ "keystone-ui--colors--theme-settings-target": "customRadio"
56
+ },
57
+ label_data: {
58
+ "keystone-ui--colors--theme-settings-target": "templateLabel",
59
+ template: "custom"
60
+ }
61
+ ) do %>
62
+ <span>Custom</span>
63
+ <% end %>
64
+ </div>
65
+ <% end %>
66
+
67
+ <%= ui_section(title: "Accent Color", spacing: :sm) do %>
68
+ <div data-keystone-ui--colors--theme-settings-target="accentPicker">
69
+ <%= ui_color_picker(name: "accent", value: current_accent_hex) %>
70
+ </div>
71
+ <% end %>
72
+
73
+ <%= ui_section(title: "Surface", spacing: :sm) do %>
74
+ <div data-keystone-ui--colors--theme-settings-target="surfacePicker">
75
+ <%= ui_color_picker(name: "surface", value: current_surface_hex) %>
76
+ </div>
77
+ <% end %>
78
+
79
+ <%= ui_section(title: "Text Color", spacing: :sm) do %>
80
+ <div data-keystone-ui--colors--theme-settings-target="textPicker">
81
+ <%= ui_color_picker(name: "text", value: current_text_hex) %>
82
+ </div>
83
+ <% end %>
@@ -1,26 +1,8 @@
1
1
  <%
2
2
  preference = KeystoneUi::Colors::ThemePreference.find_or_initialize_by(owner: person)
3
3
 
4
- current_accent_hex = if preference.accent&.start_with?("#")
5
- preference.accent
6
- else
7
- KeystoneUi::Colors::Palettes.accent(preference.accent || "blue")[500]
8
- end
9
-
10
- current_surface_hex = if preference.surface&.start_with?("#")
11
- preference.surface
12
- else
13
- KeystoneUi::Colors::Palettes.surface(preference.surface || "zinc")[500]
14
- end
15
-
16
- current_text_hex = KeystoneUi::Colors::CustomColours.new(
17
- template_name: preference.template_name,
18
- surface: preference.surface,
19
- text: preference.text
20
- ).text
21
-
22
4
  selected_template = preference.template_name
23
- custom_selected = preference.accent&.start_with?("#") || (selected_template.blank? && preference.persisted?)
5
+ choice = KeystoneUi::Colors::ColourChoice.new(person: person, account: account)
24
6
  %>
25
7
 
26
8
  <%= ui_panel(padding: :lg) do %>
@@ -30,70 +12,14 @@
30
12
  data: { turbo: false, controller: "keystone-ui--colors--theme-settings", selected_template: selected_template }
31
13
  ) do |form| %>
32
14
  <div style="display: flex; flex-direction: column; gap: 2rem;">
33
- <%= ui_section(title: "Theme", spacing: :sm) do %>
34
- <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
35
- <% KeystoneUi::Colors::Templates.names.each do |name| %>
36
- <% template = KeystoneUi::Colors::Templates[name] %>
37
- <% accent_hex = KeystoneUi::Colors::Palettes.accent(template[:accent])[500] %>
38
- <% surface_hex = KeystoneUi::Colors::Palettes.surface(template[:surface])[500] %>
39
- <%= ui_option_card(
40
- name: "template_name",
41
- value: name,
42
- selected: selected_template == name.to_s,
43
- input_data: {
44
- action: "keystone-ui--colors--theme-settings#selectTemplate",
45
- "accent-hex": accent_hex,
46
- "surface-hex": surface_hex,
47
- "text-hex": template[:text]
48
- },
49
- label_data: {
50
- "keystone-ui--colors--theme-settings-target": "templateLabel",
51
- template: name
52
- }
53
- ) do %>
54
- <span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 9999px; background-color: <%= accent_hex %>;"></span>
55
- <span style="display: inline-block; width: 1rem; height: 1rem; border-radius: 0.25rem; background-color: <%= surface_hex %>;"></span>
56
- <span><%= template[:label] %></span>
57
- <% end %>
58
- <% end %>
59
- <%= ui_option_card(
60
- name: "template_name",
61
- value: "",
62
- selected: custom_selected,
63
- input_data: {
64
- "keystone-ui--colors--theme-settings-target": "customRadio"
65
- },
66
- label_data: {
67
- "keystone-ui--colors--theme-settings-target": "templateLabel",
68
- template: "custom"
69
- }
70
- ) do %>
71
- <span>Custom</span>
72
- <% end %>
73
- </div>
74
- <% end %>
75
-
76
- <%= ui_section(title: "Accent Color", spacing: :sm) do %>
77
- <div data-keystone-ui--colors--theme-settings-target="accentPicker">
78
- <%= ui_color_picker(name: "accent", value: current_accent_hex) %>
79
- </div>
80
- <% end %>
81
-
82
- <%= ui_section(title: "Surface", spacing: :sm) do %>
83
- <div data-keystone-ui--colors--theme-settings-target="surfacePicker">
84
- <%= ui_color_picker(name: "surface", value: current_surface_hex) %>
85
- </div>
86
- <% end %>
87
-
88
- <%= ui_section(title: "Text Color", spacing: :sm) do %>
89
- <div data-keystone-ui--colors--theme-settings-target="textPicker">
90
- <%= ui_color_picker(name: "text", value: current_text_hex) %>
91
- </div>
15
+ <% if choice.person_chooses? %>
16
+ <%= render partial: "keystone_ui/colors/settings/colours", locals: { preference: preference } %>
92
17
  <% end %>
93
18
 
94
19
  <%= ui_section(title: "Mode", spacing: :sm) do %>
95
20
  <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
96
21
  <% KeystoneUi::Colors::ThemePreference::MODES.each do |mode| %>
22
+ <% next if mode == "custom" && !(choice.person_chooses? || choice.custom_offered?) %>
97
23
  <%= ui_radio_card(name: "mode", value: mode, label: mode.capitalize, checked: (preference.mode || KeystoneUi::Colors.configuration.default_mode) == mode) %>
98
24
  <% end %>
99
25
  </div>
@@ -6,6 +6,7 @@ class CreateKeystoneUiColorsThemePreferences < ActiveRecord::Migration[<%= Activ
6
6
  t.string :template_name
7
7
  t.string :mode
8
8
  t.string :text
9
+ t.boolean :members_choose, default: true, null: false
9
10
  t.references :owner, polymorphic: true, null: false
10
11
  t.timestamps
11
12
  end
@@ -0,0 +1,5 @@
1
+ class AddMembersChooseToKeystoneUiColorsThemePreferences < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ add_column :keystone_ui_colors_theme_preferences, :members_choose, :boolean, default: true, null: false
4
+ end
5
+ end
@@ -27,6 +27,13 @@ module KeystoneUi
27
27
  )
28
28
  end
29
29
 
30
+ def add_members_choose_migration
31
+ migration_template(
32
+ File.expand_path("templates/add_members_choose_to_keystone_ui_colors_theme_preferences.rb.erb", __dir__),
33
+ "db/migrate/add_members_choose_to_keystone_ui_colors_theme_preferences.rb"
34
+ )
35
+ end
36
+
30
37
  def copy_stimulus_controller
31
38
  copy_file "theme_settings_controller.js",
32
39
  "app/javascript/controllers/keystone_ui/colors/theme_settings_controller.js"
@@ -3,7 +3,7 @@
3
3
  module KeystoneUi
4
4
  module Colors
5
5
  class Configuration
6
- attr_accessor :current_owner_method, :authentication_method, :default_template, :default_accent, :default_surface, :default_mode, :default_background, :default_text, :layout
6
+ attr_accessor :current_owner_method, :authentication_method, :default_template, :default_accent, :default_surface, :default_mode, :default_background, :default_text, :account_colors, :current_account_method, :layout
7
7
 
8
8
  def initialize
9
9
  @current_owner_method = :current_user
@@ -14,6 +14,8 @@ module KeystoneUi
14
14
  @default_mode = "light"
15
15
  @default_background = "#ffffff"
16
16
  @default_text = "#18181b"
17
+ @account_colors = true
18
+ @current_account_method = nil
17
19
  @layout = "application"
18
20
  end
19
21
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module KeystoneUi
4
4
  module Colors
5
- VERSION = "0.3.1"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui-colors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -82,17 +82,22 @@ files:
82
82
  - app/helpers/keystone_ui/colors/palette_helper.rb
83
83
  - app/javascript/keystone_ui/colors/theme_settings_controller.js
84
84
  - app/models/keystone_ui/colors/application_record.rb
85
+ - app/models/keystone_ui/colors/colour_choice.rb
85
86
  - app/models/keystone_ui/colors/custom_colours.rb
86
87
  - app/models/keystone_ui/colors/forget_the_theme_choice.rb
87
88
  - app/models/keystone_ui/colors/kept.rb
89
+ - app/models/keystone_ui/colors/pick_account_colours.rb
88
90
  - app/models/keystone_ui/colors/pick_colours.rb
89
91
  - app/models/keystone_ui/colors/refusal.rb
90
92
  - app/models/keystone_ui/colors/theme_preference.rb
93
+ - app/views/keystone_ui/colors/settings/_account_picker.html.erb
94
+ - app/views/keystone_ui/colors/settings/_colours.html.erb
91
95
  - app/views/keystone_ui/colors/settings/_picker.html.erb
92
96
  - app/views/keystone_ui/colors/settings/show.html.erb
93
97
  - config/routes.rb
94
98
  - lib/generators/keystone_ui/colors/install/install_generator.rb
95
99
  - lib/generators/keystone_ui/colors/install/templates/create_keystone_ui_colors_theme_preferences.rb.erb
100
+ - lib/generators/keystone_ui/colors/update/templates/add_members_choose_to_keystone_ui_colors_theme_preferences.rb.erb
96
101
  - lib/generators/keystone_ui/colors/update/templates/add_mode_to_keystone_ui_colors_theme_preferences.rb.erb
97
102
  - lib/generators/keystone_ui/colors/update/templates/add_text_to_keystone_ui_colors_theme_preferences.rb.erb
98
103
  - lib/generators/keystone_ui/colors/update/update_generator.rb