phlex-style_variants 0.0.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: 907f0ff19a28e5800d9a1d4a0ac29697f0f8425f0c64f1547c4ce012f3a6574e
4
+ data.tar.gz: 1759598841f67559cde325ad73930280e05640896b9ba19db344e20cdf3b312d
5
+ SHA512:
6
+ metadata.gz: dfcd75133c9301daf29326149906cbea0215fafc48fcb2136cb6baf3af0cab3179445bb0a6f70eaa05260ae1977cb93d9e3d8c4a02b65d61fe37e348a5ff82d3
7
+ data.tar.gz: 89e7feffcef312ed19a97af4043d5c338dc767e1ea9be457004b6b32a9cfa277a0e2ba6a107ce464c3ab43cc534ba0c3f9782f253610bce4b877997e785e22dd
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 omarluq
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # phlex-style_variants
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/phlex-style_variants)](https://rubygems.org/gems/phlex-style_variants)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/phlex-style_variants)](https://www.ruby-toolbox.com/projects/phlex-style_variants)
5
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/omarluq/phlex-style_variants/ci.yml)](https://github.com/omarluq/phlex-style_variants/actions/workflows/ci.yml)
6
+ [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/omarluq/phlex-style_variants)](https://codeclimate.com/github/omarluq/phlex-style_variants)
7
+
8
+ Powerful Variant API for phlex component built on top of Literal
9
+
10
+ > [!WARNING]
11
+ > This gem is currently in development and is considered unstable. The API is subject to change, and you may encounter bugs or incomplete features.
12
+ > Use it at your own risk, and contribute by reporting issues or suggesting improvements!
13
+
14
+ ---
15
+
16
+ - [Quick start](#quick-start)
17
+ - [Support](#support)
18
+ - [License](#license)
19
+ - [Code of conduct](#code-of-conduct)
20
+ - [Contribution guide](#contribution-guide)
21
+
22
+ ## Features
23
+
24
+ - Declarative Style Definitions: Easily define base styles and variant groups using a clean DSL.
25
+ - Automatic Enum Generation: Automatically generates enum classes for variant groups, ensuring type safety and easy usage.
26
+ - Inheritance Support: Create style sets that inherit from other style sets, promoting DRY (Don't Repeat Yourself) principles.
27
+ - Variant Predicates: Built-in predicate methods to check active variants (e.g., `size.md?`).
28
+ - Flexible Compilation: Compile styles based on active variants, allowing dynamic class generation.
29
+ - Seamless Integration with Phlex: Designed to work effortlessly with Phlex components, enhancing their styling capabilities.
30
+
31
+ ## Quick start
32
+
33
+ ```
34
+ gem install phlex-style_variants
35
+ ```
36
+
37
+ ```ruby
38
+ require "phlex/style_variants"
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```rb
44
+ require "phlex/style_variants"
45
+
46
+ class Button < Phlex::HTML
47
+ include Phlex::StyleVariants
48
+
49
+ # Define the primary style set
50
+ style do
51
+ base { 'inline-block px-4 py-2 rounded font-semibold' }
52
+
53
+ variants do
54
+ color do
55
+ primary { 'bg-blue-500 text-white' }
56
+ danger { 'bg-red-500 text-white' }
57
+ end
58
+
59
+ size do
60
+ sm { 'text-sm' }
61
+ md { 'text-base' }
62
+ lg { 'text-lg' }
63
+ end
64
+ end
65
+ end
66
+
67
+ # Define a named style set called :wrapper with inheritance
68
+ style(:wrapper) do
69
+ base { 'p-4 bg-gray-100' }
70
+
71
+ variants do
72
+ border_style do
73
+ none { '' }
74
+ dashed { 'border-2 border-dashed border-gray-400' }
75
+ end
76
+ end
77
+ end
78
+
79
+ prop :text, String, default: -> { 'Click Me!' }, reader: :public
80
+
81
+ def view_template
82
+ div(class: style(:wrapper, border_style:).compiled) do
83
+ button(class: style(color:, size:).compiled) do
84
+ text
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # Rendering the Button component
91
+ button = Button.new(
92
+ text: 'Delete',
93
+ color: Button::ColorEnum::Danger,
94
+ size: Button::SizeEnum::Lg,
95
+ border_style: Button::BorderStyleEnum::Dashed
96
+ )
97
+ button.call
98
+ ```
99
+
100
+ output:
101
+
102
+ ```html
103
+ <div class="p-4 bg-gray-100 border-2 border-dashed border-gray-400">
104
+ <button
105
+ class="inline-block px-4 py-2 rounded font-semibold bg-red-500 text-white text-lg"
106
+ >
107
+ Delete
108
+ </button>
109
+ </div>
110
+ ```
111
+
112
+ ## Support
113
+
114
+ If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/omarluq/phlex-style_variants/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
115
+
116
+ ## License
117
+
118
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
119
+
120
+ ## Code of conduct
121
+
122
+ Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
123
+
124
+ ## Contribution guide
125
+
126
+ Pull requests are welcome!
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/phlex/style_variants/builder.rb
4
+
5
+ module Phlex
6
+ module StyleVariants
7
+ class Builder
8
+ def build(&)
9
+ @variants = {}
10
+ instance_eval(&) if block_given?
11
+ @variants
12
+ end
13
+
14
+ def method_missing(name, &)
15
+ group = VariantGroupBuilder.new(name)
16
+ group.instance_eval(&) if block_given?
17
+ @variants[name.to_sym] = group.options
18
+ end
19
+
20
+ def respond_to_missing?(name, include_private = false)
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/phlex/style_variants/config.rb
4
+
5
+ module Phlex
6
+ module StyleVariants
7
+ # Configuration class to hold multiple style sets
8
+ class Config < Literal::Struct
9
+ prop :styles, _Hash(_Symbol, Set), default: -> { {} }
10
+
11
+ # Define a new style set
12
+ def define(name, &)
13
+ styles[name.to_sym] = Set.new
14
+ styles[name.to_sym].instance_eval(&)
15
+ end
16
+
17
+ # Compile a style by name and variants
18
+ def compile(name, **variants)
19
+ style_set = styles[name.to_sym]
20
+ return '' unless style_set
21
+
22
+ style_set.compile(**variants)
23
+ end
24
+
25
+ # Create a hard copy of the config, duplicating each style set
26
+ def hard_copy
27
+ copied_styles = styles.transform_values(&:dup)
28
+ self.class.new(styles: copied_styles)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/phlex/style_variants/set.rb
4
+
5
+ module Phlex
6
+ module StyleVariants
7
+ # Represents a set of styles with base and variants
8
+ class Set < Literal::Struct
9
+ # Make 'base' _Nilable and default to nil
10
+ prop :__base, _Nilable(String), default: -> {}
11
+ prop :__variants, _Hash(_Symbol, _Hash(_Symbol, String)), default: -> { {} }
12
+
13
+ # Define base styles
14
+ def base(&)
15
+ self.__base = instance_eval(&)
16
+ end
17
+
18
+ # Define variants using the Builder DSL
19
+ def variants(&)
20
+ builder = Builder.new
21
+ self.__variants = builder.build(&)
22
+ end
23
+
24
+ # Compile the class string based on variants
25
+ def compile(**opts)
26
+ classes = __base ? __base.dup : ''
27
+
28
+ opts.each do |variant_group, variant_value|
29
+ classes += " #{variant_value.value}"
30
+ end
31
+
32
+ classes.strip
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/phlex/style_variants/instance.rb
4
+
5
+ module Phlex
6
+ module StyleVariants
7
+ # Represents the compiled classes and variant enums
8
+ class StyleInstance < Literal::Struct
9
+ prop :compiled, String
10
+ prop :variants, _Hash(_Symbol, Literal::Enum), default: -> { {} }
11
+
12
+ # Delegate variant predicate methods (e.g., size.md?) to the enums
13
+ def method_missing(name, *args, &block)
14
+ if @variants.key?(name.to_sym) && @enums.key?(name.to_sym)
15
+ @variants[name.to_sym].__send__("#{variants[name.to_sym]}?")
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def respond_to_missing?(name, include_private = false)
22
+ @variants.key?(name.to_sym) || super
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module StyleVariants
5
+ # Helper class to build individual variants within a group
6
+ class VariantGroupBuilder
7
+ attr_reader :options
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @options = {}
12
+ end
13
+
14
+ def method_missing(option_name, &)
15
+ @options[option_name.to_sym] = instance_eval(&)
16
+ end
17
+
18
+ def respond_to_missing?(name, include_private = false)
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module StyleVariants
5
+ VERSION = '0.0.0'
6
+ end
7
+ end
@@ -0,0 +1,129 @@
1
+ # lib/phlex/style_variants/style_variants.rb
2
+
3
+ # frozen_string_literal: true
4
+
5
+ require 'literal'
6
+ require 'debug'
7
+
8
+ module Phlex
9
+ module StyleVariants
10
+ autoload :VERSION, 'phlex/style_variants/version'
11
+ autoload :Builder, 'phlex/style_variants/builder'
12
+ autoload :VariantGroupBuilder, 'phlex/style_variants/variant_group_builder'
13
+ autoload :Config, 'phlex/style_variants/config'
14
+ autoload :Set, 'phlex/style_variants/set'
15
+ autoload :StyleInstance, 'phlex/style_variants/style_instance'
16
+
17
+ # Define ClassMethods for style definitions
18
+ module ClassMethods
19
+ # Define a new style set with optional inheritance
20
+ # Usage:
21
+ # style do
22
+ # base { "..." }
23
+ # variants do
24
+ # color do
25
+ # primary { "..." }
26
+ # danger { "..." }
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # style(:wrapper, inherit: true) do
32
+ # base { "..." }
33
+ # variants do
34
+ # border { "..." }
35
+ # end
36
+ # end
37
+
38
+ def style_config
39
+ @style_config ||= if superclass.respond_to?(:style_config)
40
+ superclass.style_config.hard_copy
41
+ else
42
+ Config.new
43
+ end
44
+ end
45
+
46
+ def style(name = default_style_name, &)
47
+ style_config.define(name.to_sym, &)
48
+
49
+ # After defining the style set, define enums and props
50
+ style_set = style_config.styles[name.to_sym]
51
+ style_set.__variants.each do |variant_group, options|
52
+ # Define Enum Class for the Variant Group
53
+ enum_class = Class.new(Literal::Enum) do
54
+ prop :value, String
55
+ options.each do |option_key, option_value|
56
+ const_set(option_key.to_s.split('_').collect(&:capitalize).join, new(value: option_value))
57
+ end
58
+ end
59
+ enum_const_name = "#{variant_group.to_s.split('_').collect(&:capitalize).join}Enum".freeze
60
+ # Assign the Enum Class to the Component's Namespace
61
+ const_set(enum_const_name, enum_class)
62
+
63
+ # Define Props for the Variant Group
64
+ # Set the first option as the default value
65
+ default_variant = options.keys.first.to_s.capitalize
66
+ prop variant_group.to_sym, enum_class, default: enum_class.const_get(default_variant), reader: :public
67
+ end
68
+ end
69
+
70
+ # Helper method to remove module namespaces
71
+ def demodulize(class_name)
72
+ class_name.split('::').last
73
+ end
74
+
75
+ # Helper method to convert CamelCase to snake_case
76
+ def underscore(camel_cased_word)
77
+ word = camel_cased_word.dup
78
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
79
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
80
+ word.tr!('-', '_')
81
+ word.downcase!
82
+ word
83
+ end
84
+
85
+ # Helper method to return the string if present, else nil
86
+ def presence(string)
87
+ (string.nil? || string.strip.empty?) ? nil : string
88
+ end
89
+
90
+ # Determine the default style name based on the class name
91
+ def default_style_name
92
+ @default_style_name ||= begin
93
+ # Get the class name as a string
94
+ class_name = name
95
+
96
+ # Demodulize: Remove module namespaces
97
+ base_name = demodulize(class_name)
98
+
99
+ # Remove "Component" or "::Component" suffix
100
+ base_name = base_name.sub(/Component$/, '')
101
+
102
+ # Convert CamelCase to snake_case
103
+ style_name = underscore(base_name)
104
+
105
+ # Return the style_name if present; otherwise, default to 'component'
106
+ presence(style_name) || 'component'
107
+ end
108
+ end
109
+ end
110
+
111
+ # Define InstanceMethods for style compilation
112
+ module InstanceMethods
113
+ # Compile styles based on provided variants
114
+ # Usage:
115
+ # style(color: :primary, size: :md).size.md? # => true
116
+ def style(name = self.class.default_style_name, **variants)
117
+ compiled = self.class.style_config.compile(name.to_sym, **variants).strip
118
+ StyleInstance.new(compiled:, variants:)
119
+ end
120
+ end
121
+
122
+ # Hook to include ClassMethods and InstanceMethods when included
123
+ def self.included(base)
124
+ base.extend(ClassMethods)
125
+ base.extend(Literal::Properties)
126
+ base.include(InstanceMethods)
127
+ end
128
+ end
129
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phlex-style_variants
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - omarluq
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: literal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ description:
28
+ email:
29
+ - omar.luqman@hey.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/phlex/style_variants.rb
37
+ - lib/phlex/style_variants/builder.rb
38
+ - lib/phlex/style_variants/config.rb
39
+ - lib/phlex/style_variants/set.rb
40
+ - lib/phlex/style_variants/style_instance.rb
41
+ - lib/phlex/style_variants/variant_group_builder.rb
42
+ - lib/phlex/style_variants/version.rb
43
+ homepage: https://github.com/omarluq/phlex-style_variants
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ bug_tracker_uri: https://github.com/omarluq/phlex-style_variants/issues
48
+ changelog_uri: https://github.com/omarluq/phlex-style_variants/releases
49
+ source_code_uri: https://github.com/omarluq/phlex-style_variants
50
+ homepage_uri: https://github.com/omarluq/phlex-style_variants
51
+ rubygems_mfa_required: 'true'
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.5.16
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: A powerful variant API for phlex components
71
+ test_files: []