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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0bee415e6501824704878be6a6acfe7cdc9104e396f66fa5d0b614471c0d9dfb
4
+ data.tar.gz: 4706be40250b8c0989e1ac2154e605afbe79519a0e61987011a40bb49b06b1c6
5
+ SHA512:
6
+ metadata.gz: 83c2b16faa7ef0132180e172aa0cd9cd0b3e6e2ab2823bdf8efbf73b0ef1941f8c759355830783e386bd28d86c5805daabcf6583274f327fd66d526b967f7de2
7
+ data.tar.gz: 2e08ac188d8d9fcdedbdeae3b99471c0b44b2368946aab1d71b9e7ffdd43744356338a954d1f1e2001b56da2b2bafd027523ebf3e71ea8cd8472aba6e54d6fb2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Katya German
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # Recurrence UI
2
+
3
+ **Recurrence UI** is a plug-and-play UI component library for Ruby on Rails that makes it easy to create, edit, and display human-readable recurrence rules for recurring events.
4
+
5
+ Built on top of [ice_cube](https://github.com/ice-cube-ruby/ice_cube), it provides reusable UI components powered by [ViewComponent](https://viewcomponent.org/) and styled with Tailwind CSS.
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/recurrence_ui.svg)](https://badge.fury.io/rb/recurrence_ui)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## ✨ Features
11
+
12
+ - **Form integration:** Ready-to-use components for adding recurrence rule editors to model creation and editing forms.
13
+ - **Human-readable descriptions:** Converts `ice_cube` recurrence rules into readable descriptions.
14
+ - **Built-in internationalization:** Supports English (`en`), Russian (`ru`), Japanese (`ja`), Belarusian (`be`), and Ukrainian (`uk`).
15
+ - **Extensible translations:** Add new locales using standard YAML translation files and configuration.
16
+ - **Flexible time zones and week starts:** Configure time zones and the first day of the week. Values can be supplied through URL parameters, session data, user preferences, or application settings, depending on your integration.
17
+ - **Customizable styles:** Adjust the color palette using CSS custom properties (design tokens).
18
+ - **Light and dark themes:** Customize the appearance to match your application.
19
+ - **Flexible model attribute mapping:** Use custom database column names for recurrence rules, time zones, durations, and event start and end times.
20
+
21
+ ## 📦 Requirements
22
+
23
+ Recurrence UI depends on the following components:
24
+
25
+ - **Ruby on Rails** >= 7.0
26
+ - **[ice_cube](https://github.com/ice-cube-ruby/ice_cube)** ~> 0.16 — recurrence rule management.
27
+ - **[ViewComponent](https://viewcomponent.org/)** >= 2.0 — reusable, encapsulated UI components.
28
+ - **Tailwind CSS** — styling, integrated through `tailwindcss-rails` or your host application's Tailwind CSS setup.
29
+
30
+ ## 🚀 Installation
31
+
32
+ Add the gem to your application's `Gemfile`:
33
+
34
+ ```ruby
35
+ gem "recurrence_ui"
36
+ ```
37
+
38
+ Then install the dependencies:
39
+
40
+ ```bash
41
+ bundle install
42
+ ```
43
+
44
+ ### Importing styles
45
+
46
+ For Tailwind CSS v4, add the following imports to your application's stylesheet, such as `app/assets/stylesheets/application.tailwind.css`:
47
+
48
+ ```css
49
+ @import "tailwindcss";
50
+ @import "recurrence_ui/application.css";
51
+ ```
52
+
53
+ Make sure the stylesheet is included in your application's asset pipeline and that your Tailwind CSS setup is compatible with the gem's styles.
54
+
55
+ ## ⚙️ Configuration
56
+
57
+ Create an initializer at `config/initializers/recurrence_ui.rb` to configure the default settings:
58
+
59
+ ```ruby
60
+ RecurrenceUi.configure do |config|
61
+ config.locale = :en
62
+ config.time_zone = "UTC"
63
+ config.week_start = 1
64
+ end
65
+ ```
66
+
67
+ Available settings:
68
+
69
+ - `locale` — the default interface locale, such as `:en`, `:ru`, `:uk`, `:be`, or `:ja`.
70
+ - `time_zone` — the default time zone.
71
+ - `week_start` — the first day of the week: `0` for Sunday or `1` for Monday.
72
+
73
+ The values above are examples. Adjust them to match your application's requirements.
74
+
75
+ ## 🛠 Model Integration
76
+
77
+ Include `RecurrenceUi::HasRecurrence` in your model and configure the mapping between the gem's attributes and your database columns.
78
+
79
+ For example:
80
+
81
+ ```ruby
82
+ class Event < ApplicationRecord
83
+ include RecurrenceUi::HasRecurrence
84
+
85
+ has_recurrence rules: :recurring_rules,
86
+ start_time: :start_at,
87
+ end_time: :end_at,
88
+ time_zone: :time_zone
89
+ duration: :duration
90
+ end
91
+ ```
92
+
93
+ ### Using custom attribute names
94
+
95
+ You can configure different attribute names for different models:
96
+
97
+ ```ruby
98
+ class Task < ApplicationRecord
99
+ include RecurrenceUi::HasRecurrence
100
+
101
+ has_recurrence rules: :recurrence_data,
102
+ start_time: :due_date
103
+ end
104
+ ```
105
+
106
+ Use the attribute names that match your application's database schema and the attributes supported by your model integration.
107
+
108
+ ## 🎨 View Integration
109
+
110
+ Render the recurrence editor inside a Rails form using `RecurrenceUi::FormContainerComponent`:
111
+
112
+ ```erb
113
+ <%= form_with model: @event do |form| %>
114
+ <%= render RecurrenceUi::FormContainerComponent.new(
115
+ form: form,
116
+ record: @event,
117
+ locale: params[:locale],
118
+ time_zone: params[:time_zone],
119
+ week_start: params[:week_start]
120
+ ) %>
121
+
122
+ <%= form.submit "Save" %>
123
+ <% end %>
124
+ ```
125
+
126
+ The `locale`, `time_zone`, and `week_start` options can be supplied dynamically when needed. If omitted, the component can use the applicable defaults provided by your integration.
127
+
128
+ ## 🌍 Internationalization
129
+
130
+ Recurrence UI currently supports the following locales:
131
+
132
+ - English (`en`)
133
+ - Russian (`ru`)
134
+ - Ukrainian (`uk`)
135
+ - Belarusian (`be`)
136
+ - Japanese (`ja`)
137
+
138
+ ### Adding a new locale
139
+
140
+ You can extend the interface with additional translations.
141
+
142
+ For example, to add German (`de`):
143
+
144
+ 1. Create a translation file named `config/locales/recurrence_ui.de.yml` in your application.
145
+ 2. Add the required translation keys, including the relevant labels, actions, duration descriptions, and other interface strings.
146
+ 3. Configure the new locale or pass it dynamically through your application's request parameters.
147
+
148
+ Make sure the translation file follows the structure expected by the gem's existing locale files.
149
+
150
+ ## 🌓 Themes and Customization
151
+
152
+ Recurrence UI uses CSS custom properties (design tokens) to control its color palette.
153
+
154
+ You can override these properties in your application's stylesheet to match your brand or customize the appearance of the interface.
155
+
156
+ For example:
157
+
158
+ ```css
159
+ :root {
160
+ --rui-primary-bg: #2563eb;
161
+ }
162
+
163
+ /* Dark theme */
164
+ .dark,
165
+ [data-rui-theme="dark"] {
166
+ --rui-bg-main: #1f2937;
167
+ --rui-text-main: #f9fafb;
168
+ }
169
+ ```
170
+
171
+ Use the CSS variables supported by the gem to customize the interface. The example above illustrates the approach; available tokens and theme selectors depend on the styles provided by your version of Recurrence UI.
172
+
173
+ ## 🤝 Contributing
174
+
175
+ Contributions, bug reports, and feature requests are welcome!
176
+
177
+ To get started:
178
+
179
+ 1. Clone the repository.
180
+ 2. Install the dependencies:
181
+
182
+ ```bash
183
+ bundle install
184
+ ```
185
+
186
+ 3. Run the test suite:
187
+
188
+ ```bash
189
+ bundle exec rake test
190
+ ```
191
+
192
+ 4. Create a branch, make your changes, and open a pull request.
193
+
194
+ Please make sure your changes are covered by tests where appropriate.
195
+ ## 🧪 Testing
196
+
197
+ *Note: The test suite is currently under active development. Contributions are very welcome!*
198
+
199
+ ### Planned Test Suite
200
+
201
+ - [ ] **Models & Concerns (`HasRecurrence`):** Test attribute mapping, JSON parsing, virtual form attributes, and `IceCube::Schedule` generation.
202
+ - [ ] **Formatter Service (`RecurrenceFormatterService`):** Validate human-readable summaries, smart date/time deduplication, and month declensions.
203
+ - [ ] **Internationalization (I18n):** Automated checks to ensure all locales (`en`, `ru`, `uk`, `be`, `ja`) share a consistent translation key structure.
204
+ - [ ] **ViewComponents:** Test proper rendering and Stimulus data-attribute bindings for form components.
205
+
206
+ ## 📄 License
207
+
208
+ Recurrence UI is available under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/recurrence_ui .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * RecurrenceUI CSS Manifest
3
+ * This file brings together design tokens and component styles.
4
+ */
5
+
6
+ /* 1. Tell Tailwind CSS v4 to index classes used inside the RecurrenceUI gem files */
7
+ @source "../../views/recurrence_ui/**/*.html.erb";
8
+ @source "../../components/recurrence_ui/**/*.html.erb";
9
+ @source "../../views/**/*.html.erb";
10
+
11
+ /* 2. Import CSS variables (theme tokens) */
12
+ @import "./tokens.css";
13
+
14
+ /* 3. Import component styles (UI classes) */
15
+ @import "./components.css";
@@ -0,0 +1,295 @@
1
+ .recurrence-ui-hidden{
2
+ display: none !important;
3
+ }
4
+
5
+ @layer components {
6
+ /* Main Container */
7
+ .rui-container {
8
+ background-color: var(--rui-bg-main);
9
+ color: var(--rui-text-main);
10
+ @apply space-y-3 w-full rounded-lg p-4 transition-colors;
11
+ }
12
+
13
+ /* Universal Input & Select Field Styles */
14
+ .rui-input,
15
+ .rui-select,
16
+ .rui-textarea {
17
+ background-color: var(--rui-input-bg);
18
+ color: var(--rui-text-main);
19
+ border-color: var(--rui-border-color);
20
+ @apply text-sm rounded-md border
21
+ shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500
22
+ disabled:opacity-50 px-2.5 py-1.5 transition-colors;
23
+ }
24
+
25
+ .rui-input:disabled,
26
+ .rui-select:disabled,
27
+ .rui-textarea:disabled {
28
+ background-color: var(--rui-input-disabled-bg);
29
+ color: var(--rui-text-muted) !important;
30
+ opacity: 0.6;
31
+ }
32
+ /* Подписи возле полей ввода (день, час, мин) */
33
+ .rui-duration-unit {
34
+ color: var(--rui-text-muted);
35
+ @apply text-sm;
36
+ }
37
+
38
+ .rui-input-sm {
39
+ @apply text-xs px-2 py-1 rounded;
40
+ }
41
+
42
+ /* Labels and Captions */
43
+ .rui-label {
44
+ color: var(--rui-text-main);
45
+ @apply block font-semibold tracking-wider mb-1;
46
+ }
47
+ .rui-inline-label {
48
+ color: var(--rui-text-main);
49
+ @apply text-sm font-medium;
50
+ }
51
+
52
+ /* Primary Buttons */
53
+ .rui-btn-primary {
54
+ background-color: var(--rui-primary-bg);
55
+ color: var(--rui-primary-text);
56
+ @apply px-3 py-1.5 text-xs font-medium rounded-md shadow-sm transition-colors;
57
+ }
58
+
59
+ .rui-btn-primary:hover:not(:disabled) {
60
+ background-color: var(--rui-primary-hover);
61
+ }
62
+
63
+ /* Condition, when date is not yet entered */
64
+ .rui-btn-primary:disabled {
65
+ background-color: var(--rui-secondary-bg);
66
+ color: var(--rui-text-muted);
67
+ border: 1px solid var(--rui-border-color);
68
+ cursor: not-allowed;
69
+ opacity: 0.6;
70
+ }
71
+
72
+
73
+ .rui-btn-primary:active:not(:disabled) {
74
+ background-color: var(--rui-primary-active);
75
+ }
76
+
77
+ /* Secondary Buttons */
78
+ .rui-btn-secondary {
79
+ background-color: var(--rui-secondary-bg);
80
+ color: var(--rui-secondary-text);
81
+ border-color: var(--rui-secondary-border);
82
+ @apply px-3 py-1.5 text-xs font-medium border rounded-lg transition-colors;
83
+ }
84
+
85
+ .rui-btn-secondary:hover {
86
+ background-color: var(--rui-secondary-hover);
87
+ }
88
+
89
+ /* Icon Buttons */
90
+ .rui-btn-icon {
91
+ color: var(--rui-text-muted);
92
+ @apply p-1 rounded transition-colors;
93
+ }
94
+
95
+ .rui-btn-icon:hover {
96
+ color: var(--rui-text-main);
97
+ }
98
+
99
+ /* Toggle Day Button Grid Item */
100
+ .rui-day-btn {
101
+ background-color: var(--rui-bg-main);
102
+ color: var(--rui-secondary-text);
103
+ border-color: var(--rui-border-color);
104
+ @apply w-full py-1.5 rounded-md text-xs font-medium border transition-all;
105
+ }
106
+
107
+ .rui-day-btn:hover {
108
+ background-color: var(--rui-hover-bg-soft);
109
+ }
110
+
111
+ .rui-day-btn[data-selected='true'] {
112
+ background-color: var(--rui-primary-bg);
113
+ color: var(--rui-primary-text);
114
+ border-color: var(--rui-primary-bg);
115
+ }
116
+
117
+ /* Section Dividers */
118
+ .rui-divider {
119
+ border-color: var(--rui-border-color);
120
+ @apply border-t my-2;
121
+ }
122
+ /* Large header for the editor */
123
+ .rui-header-title {
124
+ color: var(--rui-text-main);
125
+ @apply text-base font-semibold tracking-tight;
126
+ }
127
+
128
+ /* Panel header for editing */
129
+ .rui-panel-header {
130
+ border-bottom: 1px solid var(--rui-border-color);
131
+ @apply flex items-center justify-between pb-2 mb-3;
132
+ }
133
+ /* Header for the editing panel (title on the left, button on the right) */
134
+ .rui-panel-header {
135
+ border-bottom: 1px solid var(--rui-border-color);
136
+ @apply flex items-center justify-between pb-2 mb-3;
137
+ }
138
+
139
+ /* Интерактивная ссылка-действие (например, "+ Добавить правило") */
140
+ .rui-link-action {
141
+ color: var(--rui-primary-bg);
142
+ cursor: pointer;
143
+ text-decoration-line: underline;
144
+ text-decoration-style: dashed;
145
+ text-underline-offset: 4px;
146
+ @apply inline-flex items-center text-xs font-medium gap-1 transition-colors select-none;
147
+ }
148
+
149
+ .rui-link-action:hover {
150
+ color: var(--rui-primary-hover);
151
+ text-decoration-style: solid;
152
+ }
153
+ .rui-accordion-link {
154
+ color: var(--rui-primary-bg);
155
+ cursor: pointer;
156
+ text-decoration-line: underline;
157
+ text-decoration-style: dashed;
158
+ text-underline-offset: 4px;
159
+ @apply text-sm font-medium transition-colors select-none;
160
+ }
161
+
162
+ .rui-accordion-link:hover {
163
+ color: var(--rui-primary-hover);
164
+ }
165
+
166
+ /* Rule Card Body Container */
167
+ .rui-card-body {
168
+ border-left: 2px solid var(--rui-primary-bg);
169
+ background-color: var(--rui-secondary-bg);
170
+ @apply mt-1 pt-2 pb-2 pl-3 space-y-2 rounded-r-md transition-colors;
171
+ }
172
+
173
+ /* Danger / Remove Action Button */
174
+ .rui-btn-remove {
175
+ color: var(--rui-text-muted);
176
+ @apply font-normal text-xs px-1 rounded transition-colors;
177
+ }
178
+
179
+ .rui-btn-remove:hover {
180
+ color: #dc2626; /* red-600 */
181
+ background-color: var(--rui-hover-bg-soft);
182
+ }
183
+ .rui-chip {
184
+ display: inline-flex;
185
+ align-items: center;
186
+ gap: 0.25rem;
187
+ padding: 0.125rem 0.5rem;
188
+ border-radius: 0.375rem;
189
+ font-size: 0.75rem;
190
+ line-height: 1rem;
191
+ font-weight: 400;
192
+ border: 1px solid var(--rui-chip-rdate-border, #a7f3d0);
193
+ background-color: var(--rui-chip-rdate-bg, #ecfdf5);
194
+ color: var(--rui-chip-rdate-text, #047857);
195
+ }
196
+
197
+ .rui-chip__remove-btn {
198
+ padding-right: 0.125rem;
199
+ transition: opacity 150ms ease-in-out;
200
+ }
201
+
202
+ .rui-chip__remove-btn:hover {
203
+ opacity: 0.75;
204
+ }
205
+ .rui-chip--exdate {
206
+ border: 1px solid var(--rui-chip-exdate-border, #fecaca);
207
+ background-color: var(--rui-chip-exdate-bg, #fef2f2);
208
+ color: var(--rui-chip-exdate-text, #b91c1c);
209
+ }
210
+
211
+ /* Interactive Summary Link & Rule Card Header Link */
212
+ .rui-summary-link {
213
+ @apply text-sm font-medium text-blue-600 dark:text-blue-400
214
+ hover:text-blue-800 dark:hover:text-blue-300
215
+ underline underline-offset-4 decoration-dashed
216
+ decoration-blue-400 hover:decoration-blue-600
217
+ dark:decoration-blue-500 dark:hover:decoration-blue-300
218
+ transition-colors cursor-pointer;
219
+ }
220
+
221
+ /* Edit Icon wrapper */
222
+ .rui-summary-icon {
223
+ @apply opacity-0 group-hover:opacity-100 transition-opacity duration-200 text-gray-400 dark:text-gray-500;
224
+ }
225
+ /* Rule Accordion Card Container */
226
+ .rui-card-container {
227
+ border: 1px solid var(--rui-border-color);
228
+ background-color: var(--rui-secondary-bg);
229
+ @apply rounded-md w-full my-1 transition-colors overflow-hidden;
230
+ }
231
+
232
+ /* Rule Header (Main Clickable Area & Border Header) */
233
+ .rui-card-header {
234
+ background-color: var(--rui-secondary-bg);
235
+ color: var(--rui-text-main);
236
+ @apply flex items-start justify-between w-full p-2.5 text-xs font-medium
237
+ cursor-pointer select-none transition-colors;
238
+ }
239
+
240
+ .rui-card-header:hover {
241
+ background-color: var(--rui-hover-bg-soft);
242
+ }
243
+
244
+ /* Full Rule Summary Text (Text wraps naturally without truncating) */
245
+ .rui-accordion-link {
246
+ color: var(--rui-primary-bg);
247
+ @apply text-xs font-medium cursor-pointer leading-relaxed transition-colors select-none whitespace-normal;
248
+ }
249
+
250
+ .rui-accordion-link:hover {
251
+ color: var(--rui-primary-hover);
252
+ }
253
+
254
+ /* Expanded Rule Card Body (Inside the same outer container) */
255
+ .rui-card-body {
256
+ background-color: var(--rui-bg-main);
257
+ border-top: 1px solid var(--rui-border-color);
258
+ @apply p-3 space-y-3 transition-colors;
259
+ }
260
+
261
+ /* Delete Rule Action Button (Positioned on the Right Top) */
262
+ .rui-btn-remove-card {
263
+ color: var(--rui-text-muted);
264
+ @apply text-xs px-1.5 py-0.5 rounded font-normal transition-colors ml-2 cursor-pointer;
265
+ }
266
+
267
+ .rui-btn-remove-card:hover {
268
+ color: #dc2626; /* red-600 */
269
+ background-color: var(--rui-hover-bg-soft);
270
+ }
271
+ /* Базовый класс мета-информации */
272
+ .rui-meta-info {
273
+ display: block;
274
+ margin-top: 0.25rem;
275
+ font-size: 0.875rem;
276
+ font-style: italic;
277
+ transition: color 0.2s ease;
278
+ }
279
+
280
+ /* 1. Главная строка правил */
281
+ .rui-meta-info--main {
282
+ color: var(--rui-rule-main-text);
283
+ }
284
+
285
+ /* 2. Блок исключений (Exdates) */
286
+ .rui-meta-info--exceptions {
287
+ color: var(--rui-rule-exception-text); /* Исключения (например, красноватый) */
288
+ }
289
+
290
+ /* 3. Блок дополнительных дат (Rdates) */
291
+ .rui-meta-info--rdates {
292
+ color: var(--rui-rule-rdate-text); /* Доп. даты (например, изумрудный) */
293
+ }
294
+
295
+ }
@@ -0,0 +1,102 @@
1
+ /* 1. Значения по умолчанию (Светлая тема) */
2
+ :root {
3
+ --rui-bg-main: #ffffff;
4
+ --rui-text-main: #111827; /* gray-900 */
5
+ --rui-text-muted: #6b7280; /* gray-500 */
6
+ --rui-border-color: #d1d5db; /* gray-300 */
7
+
8
+ --rui-input-bg: #ffffff;
9
+ --rui-input-disabled-bg: #f3f4f6;
10
+
11
+ /* Primary (Акцентный цвет: кнопки, выбранные дни) */
12
+ --rui-primary-bg: #2563eb; /* blue-600 */
13
+ --rui-primary-hover: #1d4ed8; /* blue-700 */
14
+ --rui-primary-active: #1e40af; /* blue-800 */
15
+ --rui-primary-text: #ffffff;
16
+
17
+ /* Secondary (Второстепенные кнопки) */
18
+ --rui-secondary-bg: #f3f4f6; /* gray-100 */
19
+ --rui-secondary-hover: #e5e7eb; /* gray-200 */
20
+ --rui-secondary-text: #374151; /* gray-700 */
21
+ --rui-secondary-border: #d1d5db;
22
+
23
+ /* Hover эффекты */
24
+ --rui-hover-bg-soft: #eff6ff; /* blue-50 */
25
+
26
+ --rui-rule-main-text: #1e293b;
27
+ --rui-rule-exception-text: #b91c1c; /* red-700 */
28
+ --rui-rule-rdate-text: #047857; /* emerald-700 */
29
+ }
30
+
31
+ /* 2. Автоматический тёмный режим (системный / Firefox DevTools) */
32
+ @media (prefers-color-scheme: dark) {
33
+ :root:not([data-rui-theme="light"]) {
34
+ --rui-bg-main: #1f2937; /* ИСПРАВЛЕНО: gray-800 (было #eff6ff) */
35
+ --rui-text-main: #f9fafb; /* gray-100 */
36
+ --rui-text-muted: #9ca3af; /* gray-400 */
37
+ --rui-border-color: #374151; /* gray-700 */
38
+
39
+ --rui-input-bg: #1f2937;
40
+ --rui-input-disabled-bg: #111827;
41
+
42
+ --rui-primary-bg: #3b82f6; /* blue-500 */
43
+ --rui-primary-hover: #2563eb;
44
+
45
+ --rui-secondary-bg: #1f2937;
46
+ --rui-secondary-hover: #374151;
47
+ --rui-secondary-text: #e5e7eb;
48
+ --rui-secondary-border: #4b5563;
49
+
50
+ --rui-hover-bg-soft: #374151; /* gray-700 */
51
+ --rui-rule-main-text: #f8fafc; /* slate-50 */
52
+ --rui-rule-exception-text: #fca5a5; /* red-300 (мягче для глаза на темном) */
53
+ --rui-rule-rdate-text: #6ee7b7; /* emerald-300 */
54
+ }
55
+ }
56
+
57
+ /* 3. Ручной тёмный режим (по классу .dark на <html> или атрибуту data-rui-theme="dark") */
58
+ .dark:not([data-rui-theme="light"]),
59
+ [data-rui-theme="dark"] {
60
+ --rui-bg-main: #1f2937;
61
+ --rui-text-main: #f9fafb;
62
+ --rui-text-muted: #9ca3af;
63
+ --rui-border-color: #374151;
64
+
65
+ --rui-input-bg: #1f2937;
66
+ --rui-input-disabled-bg: #111827;
67
+
68
+ --rui-primary-bg: #3b82f6;
69
+ --rui-primary-hover: #2563eb;
70
+
71
+ --rui-secondary-bg: #1f2937;
72
+ --rui-secondary-hover: #374151;
73
+ --rui-secondary-text: #e5e7eb;
74
+ --rui-secondary-border: #4b5563;
75
+
76
+ --rui-hover-bg-soft: #374151;
77
+ --rui-rule-main-text: #f8fafc; /* slate-50 */
78
+ --rui-rule-exception-text: #fca5a5; /* red-300 (мягче для глаза на темном) */
79
+ --rui-rule-rdate-text: #6ee7b7; /* emerald-300 */
80
+ }
81
+
82
+ /* 4. Принудительный режим "Только светлая тема" (высший приоритет) */
83
+ [data-rui-theme="light"] {
84
+ --rui-bg-main: #ffffff;
85
+ --rui-text-main: #111827;
86
+ --rui-text-muted: #6b7280;
87
+ --rui-border-color: #d1d5db;
88
+ --rui-input-bg: #ffffff;
89
+ --rui-input-disabled-bg: #f3f4f6;
90
+ --rui-primary-bg: #2563eb;
91
+ --rui-primary-hover: #1d4ed8;
92
+ --rui-primary-active: #1e40af;
93
+ --rui-primary-text: #ffffff;
94
+ --rui-secondary-bg: #f3f4f6;
95
+ --rui-secondary-hover: #e5e7eb;
96
+ --rui-secondary-text: #374151;
97
+ --rui-secondary-border: #d1d5db;
98
+ --rui-hover-bg-soft: #eff6ff;
99
+ --rui-rule-main-text: #1e293b;
100
+ --rui-rule-exception-text: #b91c1c;
101
+ --rui-rule-rdate-text: #047857;
102
+ }