utility_palettes 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 34b5bd3eb2c8d5da22989e14983ccb8e47eb9978f10d50bd8e2b5bf77c15e3d5
4
+ data.tar.gz: 0ff299a1d1f60b7f05111496023a23ced487f06c2ec38fcf750c94dc4e9bd1a1
5
+ SHA512:
6
+ metadata.gz: 04c3c7333c77242ce88fcc0cbdf4aef4bf7ce0bda9e1fcf3f2c55a15c541f6767038d69b6036689dcef17c59b43444ebd0f80b392236d58a99c11efb2e447234
7
+ data.tar.gz: 2c40080c0e2aa1eb7746eadfebefa661007864c095d393de758167573354fd67a8e1dcf539fdc2a7203fdb1e758abbdcf1c2b57f5a1ff82e76bac226f387f6a4
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # UtilityPalettes
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/utility_palettes`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ > Generate your own colour palettes in an instance.
6
+
7
+ Utility Palettes is an ruby gem package for use in ruby or other projects that generates palettes and shades based on supplied colours.
8
+ Take a single colour and generate a full tailwind-style set of "absolute" shades ranging from -50 to -900 where the given colour is inserted into the range where it suits best.
9
+ Or generate "relative" shades that have the supplied colour at it's core and add a -light and -dark shade.
10
+
11
+ ## Installation
12
+
13
+ Install the gem and add to the application's Gemfile by executing:
14
+
15
+ ```bash
16
+ bundle add utility_palettes
17
+ ```
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ ```bash
22
+ gem install utility_palettes
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ To get the config full of
28
+
29
+ ```bash
30
+ rails generate utility_palettes:config
31
+ ```
32
+
33
+ to generate the palettes
34
+
35
+ ```bash
36
+ rails generate utility_palettes:generate
37
+ ```
38
+
39
+ ### Environment
40
+
41
+ Set the environment that the sub-data will be read in, meaning that the palette generator can only be run in certain environments.
42
+
43
+ ### Defaults
44
+
45
+ Should the default colours be included for the palette generator. Even if they are included, they can be overwritten by the user's own colour of the same name:
46
+
47
+ | Option | Default | Possible Values |
48
+ | :--------- | :-----: | :-------------: |
49
+ | absolutes | true | true / false |
50
+ | relatives | true | true / false |
51
+ | singles | true | true / false |
52
+
53
+ ### Output
54
+
55
+ How the output file and values should be written:
56
+
57
+ | Option | Description | Default | Possible Values |
58
+ | :------ | :-------------------------------------------------------------------------------------- | :-----: | :-----------------------------------------------------: |
59
+ | dated | Should a timestamp be included in the filenames | false | true / false |
60
+ | files | The types of files the palette should be output in, written as a comma separated string | json | json, scss, css |
61
+ | format | The colour syntax the output colours should be written in | rgb | rgb, hsl, hsv, hsb, cmyk, cielab, lab, cielch, lch, hex |
62
+ | prefix | A string that is appended to the start of all colour names, i.e. 'tw-' | '' | <any string> |
63
+ | suffix | A string that is appended to the end of all colour names, i.e. '-col' | '' | <any string> |
64
+
65
+ The JSON output file will appear at the top-level of your project, while SCSS and CSS are defined to go to `app/assets/stylesheets`.
66
+
67
+ ### Method (WIP)
68
+
69
+ It allos you to determine how you want the colours to be adjusted to create the variance in your palette, but for now all colour adjustments are made by changing the HSL values as it is the best combination of the simple yet effective and accurate method available.
70
+ In the future you will be able to shift the colours by changing values for different colour models and colour spaces; like RGB, CieLAB, OkLCH and others.
71
+
72
+ ### Steps (WIP)
73
+
74
+ Defines the percentage you want each colour to be adjusted by when moving through the colour palette. For now all steps are for the HSL method of adjusting colours
75
+ In the future you will be able to shift the colours by changing values for different colour models and colour spaces; like RGB, CieLAB, OkLCH and others.
76
+
77
+ | Option | Description | Default | Possible Values |
78
+ | :------ | :----------------------------------: | :-----: | :-------------: |
79
+ | h | How much to adjust the hue by | 0 | 0 - 100 |
80
+ | s | How much to adjust the saturation by | 2 | 0 - 100 |
81
+ | l | How much to adjust the lightness by | 9 | 0 - 100 |
82
+
83
+ ### Absolutes
84
+
85
+ Here you would define colour names and values that you would like to create an "absolute palette"; where for each colour given, a range of colours are output with -50, -100, ..., -900 suffixes using that colour as the base.
86
+
87
+ ### Relatives
88
+
89
+ Here you would define colour names and values that you would like to create an "relative palette"; where for each colour given, a -light and -dark colour are generated one step up and down from the base colour.
90
+ If a colour is included in both the "absolutes" and "relatives" sections, then the -light and -dark colours will match with colours in the absolute palette.
91
+
92
+ ### Singles
93
+
94
+ Here you would define colour names and values that you would like copied directly to the output as you have defined them.
95
+
96
+ ## Pipeline
97
+
98
+ Things that will hopefully be added in future development:
99
+
100
+ - Finishing the overall palette spec file
101
+ - Migrate off the reliance of Rails Generators
102
+ - Different colour model and space methods for adapting colours, and the steps to go with them
103
+ - Add more defaults such as different Tailwind version colours
104
+ - Have a view that can be copied to apps to allow users the ability to quickly review the colours generated
105
+ - Finish the '$' copy functionality to use colours defined in the config for other colours later in the generation process
106
+
107
+ ## Development
108
+
109
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
110
+
111
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
112
+
113
+ ## Contributing
114
+
115
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/louiswdavis/utility_palettes>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/louiswdavis/utility_palettes/blob/master/CODE_OF_CONDUCT.md).
116
+
117
+ ## License
118
+
119
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
120
+
121
+ ## Code of Conduct
122
+
123
+ Everyone interacting in the UtilityPalettes project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/louiswdavis/utility_palettes/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,36 @@
1
+ development:
2
+ utility_palettes:
3
+ defaults:
4
+ absolutes: true
5
+ relatives: true
6
+ singles: true
7
+ output:
8
+ dated: false
9
+ files: 'json, scss, css'
10
+ format: 'hex'
11
+ prefix: ''
12
+ suffix: ''
13
+ method: 'hsl'
14
+ steps:
15
+ h: 0
16
+ s: 3
17
+ l: 8
18
+ absolutes:
19
+ teal: '#2cb1bc'
20
+ orange: '#e35f00'
21
+ green: '#28b840'
22
+ purple: '#4f3bbf'
23
+ relatives:
24
+ success: '#28b840'
25
+ information: '#2cb1bc'
26
+ singles:
27
+ grey-50: '#fafafa'
28
+ grey-100: '#f5f5f5'
29
+ grey-200: '#e5e5e5'
30
+ grey-300: '#d4d4d4'
31
+ grey-400: '#a3a3a3'
32
+ grey-500: '#737373'
33
+ grey-600: '#525252'
34
+ grey-700: '#404040'
35
+ grey-800: '#262626'
36
+ grey-900: '#171717'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ module Generators
5
+ class ConfigGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('../../..', __dir__)
7
+
8
+ def copy_config
9
+ copy_file 'lib/generators/templates/config/utility_palettes.yml', 'config/utility_palettes.yml'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ module Generators
5
+ class GenerateGenerator < Rails::Generators::Base
6
+ def generate_utility_palettes
7
+ config = {}
8
+
9
+ if File.exist?('config/utility_palettes.yml') && defined?(Rails.application.config_for)
10
+ config = Rails.application.config_for(:utility_palettes).dig('utility_palettes')
11
+ elsif File.exist?('config/utility_palettes.json')
12
+ config = JSON.parse(File.read('config/utility_palettes.json')).dig(Rails.env.to_s, 'utility_palettes')
13
+ end
14
+
15
+ if !config.is_a?(Hash)
16
+ self.class.config_format_warn
17
+ elsif config.dig('disabled') == true
18
+ self.class.disabled_warn
19
+ else
20
+ UtilityPalettes::Palettes.generate(config)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def self.config_format_warn
27
+ warn 'ERROR: Utility Palettes config is not formatted as a hash for the "utility_palettes" value'
28
+ end
29
+
30
+ def self.disabled_warn
31
+ warn 'ERROR: Utility Palettes is disabled for this environment'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Configuration
5
+ def self.setup(config)
6
+ # default increment steps
7
+ default_increments = UtilityPalettes::Configuration.defaults
8
+
9
+ user_increments = config.dig(:steps) || {}
10
+
11
+ h_step = user_increments.dig(:h) || default_increments.dig('hsl', :h)
12
+ s_step = user_increments.dig(:s) || default_increments.dig('hsl', :s)
13
+ l_step = user_increments.dig(:l) || default_increments.dig('hsl', :l)
14
+
15
+ { h_step: h_step, s_step: s_step, l_step: l_step }
16
+ end
17
+
18
+ def self.defaults
19
+ {
20
+ 'rgb' => { r: '7%', g: '7%', b: '7%' },
21
+ 'hsl' => { h: 0, s: 2, l: 9 }
22
+ }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Defaults
5
+ def self.absolutes
6
+ {
7
+ 'red' => 'hsl(2, 78%, 64%)',
8
+ 'rust' => 'hsl(16, 82%, 62%)',
9
+ 'orange' => 'hsl(31, 90%, 65%)',
10
+ 'gold' => 'hsl(46, 93%, 54%)',
11
+ 'yellow' => 'hsl(58, 87%, 55%)',
12
+ 'pear' => 'hsl(80, 74%, 57%)',
13
+ 'green' => 'hsl(110, 69%, 58%)',
14
+ 'seaside' => 'hsl(156, 78%, 57%)',
15
+ 'cyan' => 'hsl(180, 69%, 37%)',
16
+ 'capri' => 'hsl(197, 90%, 46%)',
17
+ 'blue' => 'hsl(214, 78%, 36%)',
18
+ 'iris' => 'hsl(265, 87%, 57%)',
19
+ 'purple' => 'hsl(279, 85%, 56%)',
20
+ 'magenta' => 'hsl(300, 64%, 66%)',
21
+ 'pink' => 'hsl(320, 74%, 66%)',
22
+ 'satin' => 'hsl(348, 74%, 57%)',
23
+ 'cement' => 'hsl(42, 6%, 87%)',
24
+ 'grey' => 'hsl(0, 3%, 46%)',
25
+ 'base' => 'hsl(0, 3%, 46%)'
26
+ }
27
+ end
28
+
29
+ def self.relatives
30
+ {
31
+ 'success' => 'hsl(110, 69%, 58%)',
32
+ 'danger' => 'hsl(2, 78%, 64%)',
33
+ 'information' => 'hsl(214, 78%, 36%)',
34
+ 'warning' => 'hsl(46, 93%, 54%)'
35
+ }
36
+ end
37
+
38
+ def self.singles
39
+ {
40
+ 'white' => '#fff',
41
+ 'black' => '#000',
42
+ 'translucent' => 'rgba(0, 0, 0, 0.45)'
43
+ }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Outputs
5
+ def self.generate(generated_palettes, config)
6
+ output_format = config.dig(:output, :format).to_s.downcase.presence
7
+ output_palettes = {}
8
+
9
+ generated_palettes.each do |label, colourize_colour|
10
+ case output_format
11
+ when 'rgb'
12
+ values = colourize_colour.rgb.values
13
+ when 'hsl'
14
+ values = colourize_colour.hsl.values
15
+ values[1] = self.append_percentage(values[1])
16
+ values[2] = self.append_percentage(values[2])
17
+ when 'hsv'
18
+ values = colourize_colour.hsv.values
19
+ values[1] = self.append_percentage(values[1])
20
+ values[2] = self.append_percentage(values[2])
21
+ when 'hsb'
22
+ values = colourize_colour.hsb.values
23
+ values[1] = self.append_percentage(values[1])
24
+ values[2] = self.append_percentage(values[2])
25
+ when 'cmyk'
26
+ values = colourize_colour.cmyk.values
27
+ values[1] = self.append_percentage(values[1])
28
+ values[2] = self.append_percentage(values[2])
29
+ values[3] = self.append_percentage(values[3])
30
+ output_format = 'device-cmyk'
31
+ when 'cielab', 'lab'
32
+ values = colourize_colour.cielab.values
33
+ values[0] = self.append_percentage(values[0])
34
+ output_format = 'lab'
35
+ when 'cielch', 'lch'
36
+ values = colourize_colour.cielch.values
37
+ values[0] = self.append_percentage(values[0])
38
+ output_format = 'lch'
39
+ when 'hex'
40
+ values = colourize_colour.hex
41
+ else
42
+ values = colourize_colour.rgb.values
43
+ output_format = 'rgb'
44
+ end
45
+
46
+ case output_format
47
+ when 'hex'
48
+ values += (colourize_colour.alpha * 255).round.to_s(16).slice(0, 2).upcase if colourize_colour.alpha < 1.0
49
+ output_palettes[label] = values
50
+ else
51
+ values << self.append_alpha(colourize_colour.alpha) if colourize_colour.alpha < 1.0
52
+ output_palettes[label] = "#{output_format}(#{values.join(' ')})"
53
+ end
54
+ end
55
+
56
+ output_palettes
57
+ end
58
+
59
+ def self.json(filename, output_palettes)
60
+ file = Tempfile.new([filename, '.json'])
61
+
62
+ File.open(file, 'w') do |f|
63
+ f.write(JSON.pretty_generate(output_palettes))
64
+ end
65
+
66
+ puts 'Exporting utility palettes JSON...'
67
+ file
68
+ end
69
+
70
+ def self.scss(filename, output_palettes)
71
+ file = Tempfile.new([filename, '.scss'])
72
+
73
+ File.open(file, 'w') do |f|
74
+ f.write(output_palettes.collect { |label, hex| "$#{label}: #{hex};" }.join("\n"))
75
+ end
76
+
77
+ puts 'Exporting utility palettes SCSS...'
78
+ file
79
+ end
80
+
81
+ def self.css(filename, output_palettes)
82
+ file = Tempfile.new([filename, '.css'])
83
+
84
+ File.open(file, 'w') do |f|
85
+ f.write(":root {\n\t#{output_palettes.collect { |label, hex| "--#{label}: #{hex};" }.join("\n\t")}\n}")
86
+ end
87
+
88
+ puts 'Exporting utility palettes CSS...'
89
+ file
90
+ end
91
+
92
+ def bespoke_property_variables
93
+ # const utilities = {
94
+ # bg: (value) => ({ 'background-color': value }),
95
+ # text: (value) => ({ 'color': value }),
96
+ # border: (value) => ({ 'border-color': value }),
97
+ # 'border-t': (value) => ({ '--tw-border-opacity': 1, 'border-top-color': value }),
98
+ # 'border-r': (value) => ({ '--tw-border-opacity': 1, 'border-right-color': value }),
99
+ # 'border-b': (value) => ({ '--tw-border-opacity': 1, 'border-bottom-color': value }),
100
+ # 'border-l': (value) => ({ '--tw-border-opacity': 1, 'border-left-color': value }),
101
+ # outline: (value) => ({ 'outline-color': value }),
102
+ # ring: (value) => ({ '--tw-ring-opacity': 1, '--tw-ring-color': value }),
103
+ # 'ring-offset': (value) => ({ '--tw-ring-offset-color': value }),
104
+ # 'shadow': (value) => ({ '--tw-shadow-color': value, '--tw-shadow': 'var(--tw-shadow-colored)' }),
105
+ # accent: (value) => ({ 'accent-color': value }),
106
+ # caret: (value) => ({ 'caret-color': value }),
107
+ # fill: (value) => ({ 'fill': value }),
108
+ # stroke: (value) => ({ 'stroke': value }),
109
+ # };
110
+ end
111
+
112
+ private
113
+
114
+ def self.append_percentage(value)
115
+ "#{value}%"
116
+ end
117
+
118
+ def self.append_alpha(alpha)
119
+ "/ #{alpha * 100}"
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Palettes
5
+ def self.generate(config)
6
+ @config = config
7
+
8
+ puts 'Generating utility palettes...'
9
+
10
+ UtilityPalettes::Validations.validate_config(@config)
11
+ @increment_steppers = UtilityPalettes::Configuration.setup(@config)
12
+
13
+ puts 'Retrieved configuration...'
14
+
15
+ default_absolutes = @config.dig(:defaults, :absolutes) == false ? [] : UtilityPalettes::Defaults.absolutes
16
+ default_relatives = @config.dig(:defaults, :relatives) == false ? [] : UtilityPalettes::Defaults.relatives
17
+ default_singles = @config.dig(:defaults, :singles) == false ? [] : UtilityPalettes::Defaults.singles
18
+
19
+ puts 'Defined default palettes...'
20
+
21
+ user_absolutes = @config.dig(:absolutes) || {}
22
+ user_relatives = @config.dig(:relatives) || {}
23
+ user_singles = @config.dig(:singles) || {}
24
+
25
+ puts 'Retrieved user palettes...'
26
+
27
+ # merging should mean any user colours will overwrite default colours with the same key names
28
+ combined_absolutes = default_absolutes.merge(user_absolutes)
29
+ combined_relatives = default_relatives.merge(user_relatives)
30
+ combined_singles = {}.merge(default_absolutes, user_absolutes, default_singles, user_singles)
31
+
32
+ @combined_samples = combined_absolutes.merge(combined_relatives).merge(combined_singles)
33
+
34
+ puts 'Merged palettes...'
35
+
36
+ converted_absolutes = self.colourize_values(combined_absolutes)
37
+ converted_relatives = self.colourize_values(combined_relatives)
38
+ converted_singles = self.colourize_values(combined_singles)
39
+
40
+ puts 'Converted palettes...'
41
+
42
+ generated_absolutes = self.palette_looper(converted_absolutes, 'absolutes')
43
+ generated_relatives = self.palette_looper(converted_relatives, 'relatives')
44
+ generated_singles = converted_singles
45
+
46
+ puts 'Generated utility palettes...'
47
+
48
+ generated_palettes = {}.merge(generated_absolutes, generated_relatives, generated_singles)
49
+ generated_palettes = self.format_palette(generated_palettes)
50
+ output_palettes = UtilityPalettes::Outputs.generate(generated_palettes, @config)
51
+
52
+ filename = 'utility_palettes'
53
+ filename += "-#{Time.zone.now.strftime('%Y%m%d-%H%M%S')}" if @config.dig(:output, :dated) == true
54
+
55
+ output_files = (@config.dig(:output, :files) || '').split(',').map(&:strip)
56
+
57
+ file = nil
58
+ file = UtilityPalettes::Outputs.json(filename, output_palettes) if output_files.blank? || output_files.include?('json')
59
+ File.rename(file.path, "#{filename}.json") if file.present?
60
+
61
+ file = nil
62
+ file = UtilityPalettes::Outputs.scss(filename, output_palettes) if output_files.include?('scss')
63
+ File.rename(file.path, "app/assets/stylesheets/#{filename}.scss") if file.present?
64
+
65
+ file = nil
66
+ file = UtilityPalettes::Outputs.css(filename, output_palettes) if output_files.include?('css')
67
+ File.rename(file.path, "app/assets/stylesheets/#{filename}.css") if file.present?
68
+
69
+ true
70
+ end
71
+
72
+ private
73
+
74
+ def self.colourize_values(colour_hash)
75
+ colourized_hash = {}
76
+
77
+ colour_hash.each do |label, colour|
78
+ begin
79
+ if colour.start_with?('$')
80
+ # if the colour label begins with $ then it is a reference to a different defined colour, so must be looked up in the main
81
+ colourized_hash[label] = ColorConverters::Color.new(@combined_samples.dig(colour[1..].to_sym))
82
+ else
83
+ colourized_hash[label] = ColorConverters::Color.new(colour)
84
+ end
85
+
86
+ # TODO
87
+ # provide a name if the label is left blank
88
+ # if label.to_s == '$blank'
89
+ # hash[colourized_hash[label].name] = hash.delete(label)
90
+ # puts "Blank colour #{colour} has been given the name: #{colourized_hash[label].name}"
91
+ # end
92
+ rescue ColorConverters::InvalidColorError => e
93
+ warn "Error processing colour #{label} with value #{colour}: #{e.message}"
94
+ end
95
+ end
96
+
97
+ colourized_hash
98
+ end
99
+
100
+ # * Palettes
101
+
102
+ # ? Multiple Colour's Palette
103
+ # a function to loop through a map of colours and create absolute or relative palettes for them all and collect this into a single map
104
+
105
+ def self.palette_looper(colour_map, method)
106
+ looped_generated_palettes = {}
107
+
108
+ colour_map.each do |label, base_colour|
109
+ # create a palette for a single colour from the providing mapping
110
+ if method == 'absolutes'
111
+ generated_swatches = UtilityPalettes::Swatch.absolute_generator(label, base_colour, @config.dig(:method), @increment_steppers)
112
+ elsif method == 'relatives'
113
+ generated_swatches = UtilityPalettes::Swatch.relative_generator(label, base_colour, @config.dig(:method), @increment_steppers)
114
+ end
115
+
116
+ # merge the colours absolute palette into the collective mapping
117
+ looped_generated_palettes = {}.merge(looped_generated_palettes, generated_swatches)
118
+ end
119
+
120
+ looped_generated_palettes
121
+ end
122
+
123
+ def self.format_palette(palettes)
124
+ palettes.compact_blank!
125
+
126
+ palettes.transform_keys!(&:to_s)
127
+ palettes.transform_keys! { |key| @config.dig(:output, :prefix) + key } if @config.dig(:output, :prefix).present?
128
+ palettes.transform_keys! { |key| key + @config.dig(:output, :suffix) } if @config.dig(:output, :suffix).present?
129
+
130
+ palettes
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'color_converters'
4
+ require 'bigdecimal'
5
+
6
+ # an increase in the 'level' makes the percieved colour darker
7
+
8
+ module UtilityPalettes
9
+ class Sequences
10
+ def self.hsl(colour, level_change, increment_steppers)
11
+ h_step = increment_steppers[:h_step]
12
+ s_step = increment_steppers[:s_step]
13
+ l_step = increment_steppers[:l_step]
14
+
15
+ # Use BigDecimal for precise decimal arithmetic
16
+ h_value = (BigDecimal(colour.hsl[:h].to_s) - BigDecimal((level_change * h_step).to_s)) % 360
17
+ s_value = (BigDecimal(colour.hsl[:s].to_s) - BigDecimal((level_change * s_step).to_s)).clamp(0, 100)
18
+ l_value = (BigDecimal(colour.hsl[:l].to_s) - BigDecimal((level_change * l_step).to_s)).clamp(0, 100)
19
+
20
+ ColorConverters::Color.new(h: h_value.to_f, s: s_value.to_f, l: l_value.to_f)
21
+ end
22
+
23
+ def self.rgb(colour, level_change, increment_steppers)
24
+ r_step = increment_steppers[:r_step] || increment_steppers[:rgb_step]
25
+ g_step = increment_steppers[:g_step] || increment_steppers[:rgb_step]
26
+ b_step = increment_steppers[:b_step] || increment_steppers[:rgb_step]
27
+
28
+ # Use BigDecimal for precise decimal arithmetic
29
+ r_value = (BigDecimal(colour.rgb[:r].to_s) - BigDecimal((level_change * r_step).to_s)).clamp(0, 255).to_f
30
+ g_value = (BigDecimal(colour.rgb[:g].to_s) - BigDecimal((level_change * g_step).to_s)).clamp(0, 255).to_f
31
+ b_value = (BigDecimal(colour.rgb[:b].to_s) - BigDecimal((level_change * b_step).to_s)).clamp(0, 255).to_f
32
+
33
+ ColorConverters::Color.new(r: r_value, g: g_value, b: b_value)
34
+ end
35
+
36
+ def tailwind(_colour, _step, _go_lighter, _increment_steppers)
37
+ # def pSBC(p, _c0, _c1, l)
38
+ # r = nil
39
+ # g = nil
40
+ # b = nil
41
+ # pP = nil
42
+ # f = nil
43
+ # t = nil
44
+
45
+ # if l
46
+ # pPr = pP * f[:r]
47
+ # f[:g]
48
+ # pPb = pP * f[:b]
49
+
50
+ # ptr = p * t[:r]
51
+ # t[:g]
52
+ # ptb = p * t[:b]
53
+
54
+ # r = (pPr + ptr).round
55
+ # g = (pPb + ptb).round
56
+ # b = (pPb + ptb).round
57
+ # else
58
+ # pPr = pP * (f[:r]**2)
59
+ # pPg = pP * (f[:g]**2)
60
+ # pPb = pP * (f[:b]**2)
61
+
62
+ # ptr = p * (t[:r]**2)
63
+ # ptg = p * (t[:g]**2)
64
+ # ptb = p * (t[:b]**2)
65
+
66
+ # r = ((pPr + ptr)**0.5).round
67
+ # g = ((pPg + ptg)**0.5).round
68
+ # b = ((pPb + ptb)**0.5).round
69
+ # end
70
+
71
+ # a = f.a
72
+ # t = t.a
73
+ # f = a >= 0 || t >= 0
74
+
75
+ # t if f && a.negative?
76
+
77
+ # a = if f
78
+ # if a.negative?
79
+ # t
80
+ # else
81
+ # t.negative? ? a : a * pP + t * p
82
+ # end
83
+ # else
84
+ # 0
85
+ # end
86
+
87
+ ColorConverters::Color.new(r: r, g: g, b: b, a: m(a * 1000) / 1000)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Swatch
5
+ # ? Single Colour's Palette
6
+ # a function to create an absolute palette that incorporates a single colour input
7
+ def self.absolute_generator(label, base_colour, method, increment_steppers)
8
+ @method = method
9
+ @increment_steppers = increment_steppers
10
+
11
+ # colours are index inversely to their lightness
12
+ base_level = UtilityPalettes::Swatch.base_lightness_index(base_colour)
13
+ generated_absolute_swatches = {}.merge({ UtilityPalettes::Swatch.label(label, base_level) => base_colour })
14
+
15
+ # # Lighter colours
16
+ # # calc the space available to create lightened colours based off the base colour
17
+ # if base_level.positive?
18
+ # (0..base_level).each do |new_level|
19
+ # new_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, new_level)
20
+ # generated_absolute_swatches = {}.merge(generated_absolute_swatches, { UtilityPalettes::Swatch.label(label, new_level) => new_colour })
21
+ # end
22
+ # end
23
+
24
+ # # Darker colours
25
+ # # calc the space available to create darkened colours based off the base colour
26
+ # if base_level < 9
27
+ # (base_level..9).each do |new_level|
28
+ # new_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, new_level)
29
+ # generated_absolute_swatches = {}.merge(generated_absolute_swatches, { UtilityPalettes::Swatch.label(label, new_level) => new_colour })
30
+ # end
31
+ # end
32
+
33
+ if base_level.positive?
34
+ (0..9).each do |new_level|
35
+ new_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, new_level)
36
+ generated_absolute_swatches = {}.merge(generated_absolute_swatches, { UtilityPalettes::Swatch.label(label, new_level) => new_colour })
37
+ end
38
+ end
39
+
40
+ generated_absolute_swatches
41
+ end
42
+
43
+ # ? Single Colour's Relative Palette
44
+ # a function to create a relative palette centred on a single colour input
45
+ def self.relative_generator(label, base_colour, method, increment_steppers)
46
+ @method = method
47
+ @increment_steppers = increment_steppers
48
+
49
+ lighter_colour = nil
50
+ darker_colour = nil
51
+
52
+ # colours are index inversely to their lightness
53
+ base_level = UtilityPalettes::Swatch.base_lightness_index(base_colour)
54
+ generated_relative_swatches = {}.merge({ label => base_colour })
55
+
56
+ # Lighter Colour
57
+ if base_level > 1
58
+ lighter_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, base_level + 2)
59
+ elsif base_level > 0
60
+ lighter_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, base_level + 1)
61
+ else
62
+ lighter_colour = nil
63
+ end
64
+
65
+ # Darker Colour
66
+ if base_level < 8
67
+ darker_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, base_level - 2)
68
+ elsif base_level < 9
69
+ darker_colour = UtilityPalettes::Swatch.generate(base_colour, base_level, base_level - 1)
70
+ else
71
+ darker_colour = nil
72
+ end
73
+
74
+ generated_relative_swatches = {}.merge(generated_relative_swatches, { "#{label}-light" => lighter_colour })
75
+ generated_relative_swatches = {}.merge(generated_relative_swatches, { "#{label}-dark" => darker_colour })
76
+
77
+ generated_relative_swatches
78
+ end
79
+
80
+ def self.base_lightness_index(colour)
81
+ 9 - (colour.hsl[:l] / 10).floor
82
+ end
83
+
84
+ def self.label(label, index)
85
+ levels = { '0' => 50, '1' => 100, '2' => 200, '3' => 300, '4' => 400, '5' => 500, '6' => 600, '7' => 700, '8' => 800, '9' => 900 }
86
+
87
+ [label, levels.dig(index.to_s)].join('-')
88
+ end
89
+
90
+ # TODO: create other sequence methods
91
+ # ? How to Calculate the next colour in the Palette
92
+ def self.generate(colour, base_level, new_level)
93
+ case @method
94
+ when 'hsl'
95
+ UtilityPalettes::Sequences.hsl(colour, new_level - base_level, @increment_steppers)
96
+ when 'rgb'
97
+ # TODO
98
+ else
99
+ UtilityPalettes::Sequences.hsl(colour, new_level - base_level, @increment_steppers)
100
+ end
101
+ end
102
+
103
+ def build_step_check
104
+ # const ALL_LEVELS = [50, 100, 200, 300, 400, 600, 700, 800, 900];
105
+ # const levels = options.levels == null ? ALL_LEVELS : options.levels.filter(level => ALL_LEVELS.includes(level));
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ class Validations
5
+ def self.validate_config(config)
6
+ sequence_options = ['hsl']
7
+ warn "ERROR: The colour sequence method you have submitted to Utility Palettes < #{config[:steps]} > is not available (#{sequence_options.to_sentence})" if !config[:method].nil? && !config[:method].to_s.in?(sequence_options)
8
+
9
+ warn "ERROR: The colour sequence steps you have submitted to Utility Palettes < #{config[:steps]} > have not been formatted as a hash" if !config[:steps].nil? && !config[:steps].is_a?(Hash)
10
+
11
+ warn "ERROR: The absolute swatches you have submitted to Utility Palettes < #{config[:absolutes]} > have not been formatted as a hash" if !config[:absolutes].nil? && !config[:absolutes].is_a?(Hash)
12
+
13
+ warn "ERROR: The relative swatches you have submitted to Utility Palettes < #{config[:relatives]} > have not been formatted as a hash" if !config[:relatives].nil? && !config[:relatives].is_a?(Hash)
14
+
15
+ warn "ERROR: The single swatches you have submitted to Utility Palettes < #{config[:singles]} > have not been formatted as a hash" if !config[:singles].nil? && !config[:singles].is_a?(Hash)
16
+
17
+ config_keys = config.keys - [:defaults, :output, :method, :steps, :absolutes, :relatives, :singles, :disabled]
18
+
19
+ warn "WARNING: Utility Palettes does not recognize the following keys; #{config_keys.join(', ')}. Please check they match the spelling of documented palette keys in order for them to be used." if config_keys.present?
20
+
21
+ true
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UtilityPalettes
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'utility_palettes/version'
4
+
5
+ require_relative 'utility_palettes/configuration'
6
+ require_relative 'utility_palettes/defaults'
7
+ require_relative 'utility_palettes/outputs'
8
+ require_relative 'utility_palettes/palettes'
9
+ require_relative 'utility_palettes/sequences'
10
+ require_relative 'utility_palettes/swatch'
11
+ require_relative 'utility_palettes/validations'
12
+
13
+ module UtilityPalettes
14
+ class Error < StandardError; end
15
+ # Your code goes here...
16
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: utility_palettes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Louis Davis
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-08-08 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: color_converters
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.2
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.2
26
+ description: Create broad colour palettes based on specific colour swatches, or more
27
+ specific light/dark variant palettes. Be in control of the colours you use and shorten
28
+ the iterative process when deciding on how to build your palettes.
29
+ email:
30
+ - LouisWilliamDavis@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - Rakefile
37
+ - lib/generators/templates/config/utility_palettes.yml
38
+ - lib/generators/utility_palettes/config_generator.rb
39
+ - lib/generators/utility_palettes/generate_generator.rb
40
+ - lib/utility_palettes.rb
41
+ - lib/utility_palettes/configuration.rb
42
+ - lib/utility_palettes/defaults.rb
43
+ - lib/utility_palettes/outputs.rb
44
+ - lib/utility_palettes/palettes.rb
45
+ - lib/utility_palettes/sequences.rb
46
+ - lib/utility_palettes/swatch.rb
47
+ - lib/utility_palettes/validations.rb
48
+ - lib/utility_palettes/version.rb
49
+ homepage: https://github.com/louiswdavis/utility_palettes
50
+ licenses:
51
+ - MIT
52
+ metadata:
53
+ homepage_uri: https://github.com/louiswdavis/utility_palettes
54
+ source_code_uri: https://github.com/louiswdavis/utility_palettes
55
+ changelog_uri: https://github.com/louiswdavis/utility_palettes/blob/master/CHANGELOG.md
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.9.3
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubygems_version: 3.6.3
71
+ specification_version: 4
72
+ summary: Quickly build colour palettes based on default or supplied colour swatches,
73
+ and export them for use in stylesheets.
74
+ test_files: []