phlex-style_variants 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []