recurrence_ui 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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +208 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/recurrence_ui_manifest.js +1 -0
  6. data/app/assets/stylesheets/recurrence_ui/application.css +15 -0
  7. data/app/assets/stylesheets/recurrence_ui/components.css +295 -0
  8. data/app/assets/stylesheets/recurrence_ui/tokens.css +102 -0
  9. data/app/components/recurrence_ui/recurrence_picker_component.html.erb +234 -0
  10. data/app/components/recurrence_ui/recurrence_picker_component.rb +204 -0
  11. data/app/controllers/recurrence_ui/application_controller.rb +32 -0
  12. data/app/helpers/recurrence_ui/application_helper.rb +23 -0
  13. data/app/javascript/recurrence_ui/index.js +22 -0
  14. data/app/javascript/recurrence_ui/recurrence_ui_controller.js +954 -0
  15. data/app/jobs/recurrence_ui/application_job.rb +4 -0
  16. data/app/mailers/recurrence_ui/application_mailer.rb +6 -0
  17. data/app/models/concerns/recurrence_ui/has_recurrence.rb +351 -0
  18. data/app/models/recurrence_ui/application_record.rb +5 -0
  19. data/app/services/recurrence_ui/recurrence_formatter_service.rb +536 -0
  20. data/app/views/layouts/recurrence_ui/application.html.erb +15 -0
  21. data/app/views/recurrence_ui/_daily.html.erb +22 -0
  22. data/app/views/recurrence_ui/_exceptions.html.erb +50 -0
  23. data/app/views/recurrence_ui/_footer_actions.html.erb +17 -0
  24. data/app/views/recurrence_ui/_monthly_by_day_of_month.html.erb +47 -0
  25. data/app/views/recurrence_ui/_monthly_by_weekday.html.erb +55 -0
  26. data/app/views/recurrence_ui/_rdates.html.erb +50 -0
  27. data/app/views/recurrence_ui/_rule_card.html.erb +61 -0
  28. data/app/views/recurrence_ui/_summary_link.html.erb +25 -0
  29. data/app/views/recurrence_ui/_termination.html.erb +44 -0
  30. data/app/views/recurrence_ui/_weekly.html.erb +44 -0
  31. data/app/views/recurrence_ui/_yearly_by_day_of_month.html.erb +63 -0
  32. data/app/views/recurrence_ui/_yearly_by_weekday.html.erb +72 -0
  33. data/config/i18n-tasks.yml +212 -0
  34. data/config/importmap.rb +3 -0
  35. data/config/locales/recurrence_ui.be.yml +245 -0
  36. data/config/locales/recurrence_ui.en.yml +210 -0
  37. data/config/locales/recurrence_ui.ja.yml +201 -0
  38. data/config/locales/recurrence_ui.ru.yml +245 -0
  39. data/config/locales/recurrence_ui.uk.yml +245 -0
  40. data/config/routes.rb +5 -0
  41. data/lib/generators/recurrence_ui/views_generator.rb +26 -0
  42. data/lib/recurrence_ui/configuration.rb +116 -0
  43. data/lib/recurrence_ui/engine.rb +57 -0
  44. data/lib/recurrence_ui/version.rb +3 -0
  45. data/lib/recurrence_ui.rb +25 -0
  46. data/lib/tasks/recurrence_ui_tasks.rake +4 -0
  47. metadata +195 -0
@@ -0,0 +1,26 @@
1
+ # lib/generators/recurrence_ui/views_generator.rb
2
+ require "rails/generators/base"
3
+
4
+ module RecurrenceUi
5
+ module Generators
6
+ class ViewsGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../../../..", __FILE__)
8
+
9
+ desc "Copies recurrence_ui views to your application for customization"
10
+
11
+ def copy_views
12
+ # 1. Copy main component template to app/components/recurrence_ui/
13
+ copy_file(
14
+ "app/components/recurrence_ui/picker_component.html.erb",
15
+ "app/components/recurrence_ui/picker_component.html.erb"
16
+ )
17
+
18
+ # 2. Copy all partials from app/views/recurrence_ui/ to app/views/recurrence_ui/
19
+ directory(
20
+ "app/views/recurrence_ui",
21
+ "app/views/recurrence_ui"
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,116 @@
1
+ # lib/recurrence_ui/configuration.rb
2
+ module RecurrenceUi
3
+ class Configuration
4
+
5
+ SUPPORTED_LOCALES = %w[en ru ja uk be].freeze # Locales for which yml files are available in the gem
6
+ DEFAULT_LOCALE = 'en'.freeze
7
+ DEFAULT_TIME_ZONE = 'UTC'.freeze
8
+ DEFAULT_WEEK_START = 0
9
+ DEFAULT_DURATION = 0 # Default duration in seconds if not specified
10
+ ALL_RULE_TYPES = %w[
11
+ never
12
+ daily
13
+ weekly
14
+ monthly_by_day_of_month
15
+ monthly_by_weekday
16
+ yearly_by_day_of_month
17
+ yearly_by_weekday
18
+ ].freeze
19
+
20
+ attr_accessor :enabled_rule_types,
21
+ :locale,
22
+ :time_zone,
23
+ :week_start,
24
+ :supported_locales,
25
+ :theme_mode,
26
+ :default_recurring_rules_field,
27
+ :default_start_time_field,
28
+ :default_end_time_field,
29
+ :default_time_zone_field,
30
+ :default_duration_field
31
+
32
+ def initialize
33
+ @locale = nil
34
+ @time_zone = nil
35
+ @week_start = nil
36
+ @duration = nil
37
+ @theme_mode = :auto # Example: :auto (default), :light (light only), :dark (dark only)
38
+ @supported_locales = SUPPORTED_LOCALES
39
+ @enabled_rule_types = ALL_RULE_TYPES.dup
40
+ @default_recurring_rules_field = :recurring_rules
41
+ @default_start_time_field = :start_at
42
+ @default_end_time_field = :end_at
43
+ @default_time_zone_field = :time_zone
44
+ @default_duration_field = :duration
45
+ end
46
+
47
+ def resolved_locale(requested = nil)
48
+ # 1. Get the candidate locale
49
+ # Priority: requested -> I18n.locale -> @locale
50
+ candidate = requested&.to_s.presence ||
51
+ (defined?(I18n) && I18n.locale&.to_s.presence) ||
52
+ @locale&.to_s.presence
53
+
54
+ # 2. Validate against supported locales
55
+ if SUPPORTED_LOCALES.include?(candidate)
56
+ candidate
57
+ else
58
+ # Log Unsupported Locale Warning: Log a warning in dev mode if an unsupported locale is received
59
+ log_unsupported_locale_warning(candidate) if candidate.present?
60
+
61
+ DEFAULT_LOCALE
62
+ end
63
+ end
64
+
65
+ def resolved_time_zone(requested = nil)
66
+ # Priority: requested -> Time.zone -> @time_zone
67
+ candidate = requested.presence ||
68
+ (defined?(Time) && Time.respond_to?(:zone) && Time.zone&.name.presence) ||
69
+ @time_zone.presence
70
+ # Check if the time zone exists in Rails
71
+ if candidate.present? && defined?(ActiveSupport::TimeZone) && ActiveSupport::TimeZone[candidate].present?
72
+ candidate
73
+ else
74
+ DEFAULT_TIME_ZONE
75
+ end
76
+ end
77
+
78
+ def resolved_week_start(requested = nil)
79
+ # Priority: requested -> Date.beginning_of_week -> @week_start
80
+ rails_week_start = defined?(Date) && Date.respond_to?(:beginning_of_week) ? (Date.beginning_of_week == :monday ? 1 : 0) : nil
81
+
82
+ raw = requested.presence ||
83
+ rails_week_start ||
84
+ @week_start.presence
85
+
86
+ normalized = normalize_week_start(raw)
87
+ [0, 1].include?(normalized) ? normalized : DEFAULT_WEEK_START
88
+ end
89
+
90
+ # Method for resolving enabled rule types with filtering and user-defined order preservation
91
+ def resolved_enabled_rule_types(passed_types = nil)
92
+ candidate = passed_types.presence || @enabled_rule_types.presence || ALL_RULE_TYPES
93
+ candidate = Array(candidate).map(&:to_s)
94
+
95
+ #Use only valid types, preserving the user-defined order
96
+ valid_types = candidate.select { |t| ALL_RULE_TYPES.include?(t) }
97
+ valid_types.presence || ALL_RULE_TYPES
98
+ end
99
+
100
+ private
101
+ # Logs a warning in development mode if an unsupported locale is received
102
+ def log_unsupported_locale_warning(locale)
103
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger && Rails.env.development?
104
+ Rails.logger.warn("[RecurrenceUi] Locale '#{locale}' is not supported by the gem. Falling back to '#{DEFAULT_LOCALE}'. Supported locales: #{SUPPORTED_LOCALES.join(', ')}")
105
+ end
106
+ end
107
+ # Normalizes the week_start value to either 0 (Sunday) or 1 (Monday)
108
+ def normalize_week_start(val)
109
+ case val&.to_s&.downcase
110
+ when '1', 'monday', 'mon' then 1
111
+ when '0', 'sunday', 'sun' then 0
112
+ else val.to_i
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,57 @@
1
+ # recurrence_ui/lib/recurrence_ui/engine.rb
2
+ require "recurrence_ui"
3
+
4
+ module RecurrenceUi
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace RecurrenceUi
7
+
8
+ # 1. Tailwind CSS v3 paths
9
+ initializer "recurrence_ui.tailwindcss" do |app|
10
+ if app.config.respond_to?(:tailwindcss)
11
+ app.config.tailwindcss.content_paths << Engine.root.join("app/components/**/*.{erb,haml,html,slim,rb}").to_s
12
+ app.config.tailwindcss.content_paths << Engine.root.join("app/views/**/*.{erb,haml,html,slim,rb}").to_s
13
+ end
14
+ end
15
+
16
+ # 2. Add importmap support for the gem
17
+ initializer "recurrence_ui.importmap", before: "importmap" do |app|
18
+ if app.config.respond_to?(:importmap)
19
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
20
+ end
21
+ end
22
+
23
+ # 3. Assets and precompilation of ONLY the necessary files
24
+ initializer "recurrence_ui.assets" do |app|
25
+ if app.config.respond_to?(:assets)
26
+ app.config.assets.paths << Engine.root.join("app/javascript")
27
+ app.config.assets.paths << Engine.root.join("app/assets/stylesheets")
28
+ app.config.assets.precompile += %w( recurrence_ui/application.css )
29
+
30
+ # Add files for precompilation
31
+ app.config.assets.precompile += %w(
32
+ recurrence_ui/index.js
33
+ recurrence_ui/recurrence_ui_controller.js
34
+ recurrence_ui.css
35
+ )
36
+ end
37
+ end
38
+
39
+ # 4. i18n locales
40
+ initializer "recurrence_ui.locales" do |app|
41
+ config.i18n.load_path += Dir[Engine.root.join("config", "locales", "*.{rb,yml}")]
42
+ end
43
+
44
+ # 5. Routes for debugging in development
45
+ initializer "recurrence_ui.append_routes" do |app|
46
+ app.routes.append do
47
+ if Rails.env.development?
48
+ get '/dev/recurrence_previews', to: 'recurrence_ui/dev/recurrence_previews#index'
49
+ end
50
+ end
51
+ end
52
+ # Add components to the global ViewComponent / ActionView search
53
+ config.to_prepare do
54
+ ApplicationController.helper(RecurrenceUi::Engine.helpers)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module RecurrenceUi
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require "rails"
2
+ require "view_component"
3
+ require "ice_cube" # This gem is used to generate recurrence rules and calculate occurrences of events based on those rules.
4
+ require "rails-i18n" # This gem is used for internationalization and localization of the application.
5
+
6
+ require "recurrence_ui/version"
7
+ require "recurrence_ui/configuration"
8
+ require "recurrence_ui/engine"
9
+
10
+ module RecurrenceUi
11
+ class << self
12
+ def configuration
13
+ @configuration ||= Configuration.new
14
+ end
15
+
16
+ def configure
17
+ yield(configuration) if block_given?
18
+ end
19
+
20
+ def config
21
+ configuration
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :recurrence_ui do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recurrence_ui
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Katerina German
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 7.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '7.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 7.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: view_component
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: ice_cube
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.16'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.16'
61
+ - !ruby/object:Gem::Dependency
62
+ name: i18n
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.14'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.14'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rails-i18n
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '7.0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '7.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: sqlite3
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.6'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.6'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rspec-rails
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '6.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '6.0'
117
+ description: Provides ViewComponents and Stimulus controllers to build flexible event
118
+ recurrence schedules using IceCube.
119
+ email:
120
+ - assoly@gmail.com
121
+ executables: []
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - MIT-LICENSE
126
+ - README.md
127
+ - Rakefile
128
+ - app/assets/config/recurrence_ui_manifest.js
129
+ - app/assets/stylesheets/recurrence_ui/application.css
130
+ - app/assets/stylesheets/recurrence_ui/components.css
131
+ - app/assets/stylesheets/recurrence_ui/tokens.css
132
+ - app/components/recurrence_ui/recurrence_picker_component.html.erb
133
+ - app/components/recurrence_ui/recurrence_picker_component.rb
134
+ - app/controllers/recurrence_ui/application_controller.rb
135
+ - app/helpers/recurrence_ui/application_helper.rb
136
+ - app/javascript/recurrence_ui/index.js
137
+ - app/javascript/recurrence_ui/recurrence_ui_controller.js
138
+ - app/jobs/recurrence_ui/application_job.rb
139
+ - app/mailers/recurrence_ui/application_mailer.rb
140
+ - app/models/concerns/recurrence_ui/has_recurrence.rb
141
+ - app/models/recurrence_ui/application_record.rb
142
+ - app/services/recurrence_ui/recurrence_formatter_service.rb
143
+ - app/views/layouts/recurrence_ui/application.html.erb
144
+ - app/views/recurrence_ui/_daily.html.erb
145
+ - app/views/recurrence_ui/_exceptions.html.erb
146
+ - app/views/recurrence_ui/_footer_actions.html.erb
147
+ - app/views/recurrence_ui/_monthly_by_day_of_month.html.erb
148
+ - app/views/recurrence_ui/_monthly_by_weekday.html.erb
149
+ - app/views/recurrence_ui/_rdates.html.erb
150
+ - app/views/recurrence_ui/_rule_card.html.erb
151
+ - app/views/recurrence_ui/_summary_link.html.erb
152
+ - app/views/recurrence_ui/_termination.html.erb
153
+ - app/views/recurrence_ui/_weekly.html.erb
154
+ - app/views/recurrence_ui/_yearly_by_day_of_month.html.erb
155
+ - app/views/recurrence_ui/_yearly_by_weekday.html.erb
156
+ - config/i18n-tasks.yml
157
+ - config/importmap.rb
158
+ - config/locales/recurrence_ui.be.yml
159
+ - config/locales/recurrence_ui.en.yml
160
+ - config/locales/recurrence_ui.ja.yml
161
+ - config/locales/recurrence_ui.ru.yml
162
+ - config/locales/recurrence_ui.uk.yml
163
+ - config/routes.rb
164
+ - lib/generators/recurrence_ui/views_generator.rb
165
+ - lib/recurrence_ui.rb
166
+ - lib/recurrence_ui/configuration.rb
167
+ - lib/recurrence_ui/engine.rb
168
+ - lib/recurrence_ui/version.rb
169
+ - lib/tasks/recurrence_ui_tasks.rake
170
+ homepage: https://github.com/assoly/recurrence_ui
171
+ licenses:
172
+ - MIT
173
+ metadata:
174
+ source_code_uri: https://github.com/assoly/recurrence_ui
175
+ changelog_uri: https://github.com/assoly/recurrence_ui/blob/main/CHANGELOG.md
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 3.1.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.5.16
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: UI component for IceCube schedule generation in Rails.
195
+ test_files: []