uikit_rails 0.1.2 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 522e1f5fedc86548fccf59afbc5f7216b338c89489ebff5dd67af799732538a8
4
- data.tar.gz: 79239da17263158fe57d5a6885a29dd355780e4da6e0ea4736b30a4712bedc03
3
+ metadata.gz: 2f91b649381153711d5c2f5a78ad89d5d739504df3158e1ce81635fdc3f2e97c
4
+ data.tar.gz: 3f808888f9f379ef227fe9513ffa2978605eccc18370f2be66a5dc65103222d8
5
5
  SHA512:
6
- metadata.gz: 537059c2b395cb9edaed5d4e96e6e2e38df6202af7dd14642eaff2c24b04d6a41810b4d9599b667c591b97187fdebef67a8bae30a33913f342ee9ac308984455
7
- data.tar.gz: fe4ba44f91755671071983995fd7961f9e7546d6b12d183d7848b1771d060781bd200356e388a32c5c5eb2d2baa6d3a95290544f9450949f669129b93ed33319
6
+ metadata.gz: b2016553a1fc9de10ba54315362484a98bd6db0a8e661612712049d28cc3f66f825710dec2c14edb9d850d50422cb8c89ef9aa33361202fff676014181ca993a
7
+ data.tar.gz: 592e733bdb3f46e3ebdd710cfc894623a573d43c65caef6ebfa48fc03427db0cddabd04dacd16530f422190175fc8809c5508349fe2890ba4e0f60cd0787154b
data/README.md CHANGED
@@ -187,6 +187,7 @@ The styleguide is self-contained — it has its own layout and styles that won't
187
187
  | `popover` | Floating content panel triggered by click |
188
188
  | `tabs` | Tabbed content panels with keyboard navigation |
189
189
  | `tooltip` | Hover/focus tooltip (top, bottom, left, right) |
190
+ | `toast` | Dismissible notifications; `position:` sets fixed corner/center (six anchors), optional auto-dismiss (Stimulus) |
190
191
  | `accordion` | Collapsible sections with single or multiple open |
191
192
  | `collapsible` | Simple expand/collapse toggle |
192
193
 
@@ -0,0 +1,59 @@
1
+ Basic toast:
2
+
3
+ <%= render Ui::Toast::Component.new do |toast| %>
4
+ <% toast.with_title do %>Done<% end %>
5
+ <% toast.with_description do %>Operation completed.<% end %>
6
+ <% end %>
7
+
8
+ Variants (default, success, destructive, warning):
9
+
10
+ <%= render Ui::Toast::Component.new(variant: :success) do |toast| %>
11
+ <% toast.with_description do %>Saved.<% end %>
12
+ <% end %>
13
+
14
+ Without dismiss button:
15
+
16
+ <%= render Ui::Toast::Component.new(dismissible: false) do |toast| %>
17
+ <% toast.with_description do %>Processing…<% end %>
18
+ <% end %>
19
+
20
+ With auto-dismiss (duration_ms; requires ui--toast Stimulus):
21
+
22
+ <%= render Ui::Toast::Component.new(duration_ms: 4000) do |toast| %>
23
+ <% toast.with_description do %>Session expires soon.<% end %>
24
+ <% end %>
25
+
26
+ Stack multiple toasts — wrap in a viewport (see toast.css), omit position on each toast:
27
+
28
+ <div class="ui-toast-viewport ui-toast-viewport--bottom-right" aria-live="polite" aria-label="Notifications">
29
+ <%= render Ui::Toast::Component.new(variant: :success) do |toast| %>
30
+ <% toast.with_description do %>First<% end %>
31
+ <% end %>
32
+ <%= render Ui::Toast::Component.new do |toast| %>
33
+ <% toast.with_description do %>Second<% end %>
34
+ <% end %>
35
+ </div>
36
+
37
+ Single toast with fixed position (wraps in .ui-toast-viewport for you):
38
+
39
+ position: :top_left, :top_center, :top_right, :bottom_left, :bottom_center, :bottom_right
40
+
41
+ <%= render Ui::Toast::Component.new(position: :top_center) do |toast| %>
42
+ <% toast.with_description do %>Centered along the top edge.<% end %>
43
+ <% end %>
44
+
45
+ For in-flow previews (not fixed to the window), add ui-toast-viewport--inline on the viewport
46
+ or viewport_html_attrs: { class: "ui-toast-viewport--inline" } when using position:.
47
+
48
+ Slots:
49
+ title — Optional heading
50
+ description — Optional supporting text
51
+ content — Default slot for extra markup
52
+
53
+ Close button uses data-action="click->ui--toast#dismiss" (removes the node after a short animation).
54
+
55
+ With custom attributes:
56
+
57
+ <%= render Ui::Toast::Component.new(class: "extra", data: { testid: "notice" }) do |toast| %>
58
+ <% toast.with_title do %>Heads up<% end %>
59
+ <% end %>
@@ -0,0 +1,40 @@
1
+ <% toast_inner = capture do %>
2
+ <% if show_icon? %>
3
+ <div class="ui-toast__icon" aria-hidden="true">
4
+ <% case variant
5
+ when :success %>
6
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
7
+ <% when :destructive %>
8
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
9
+ <% when :warning %>
10
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></svg>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
14
+ <div class="ui-toast__main">
15
+ <% if title? %>
16
+ <div class="ui-toast__title"><%= title %></div>
17
+ <% end %>
18
+ <% if description? %>
19
+ <div class="ui-toast__description"><%= description %></div>
20
+ <% end %>
21
+ <%= content %>
22
+ </div>
23
+ <% if dismissible %>
24
+ <button type="button" class="ui-toast__close" data-action="click->ui--toast#dismiss" aria-label="Dismiss notification">
25
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
26
+ </button>
27
+ <% end %>
28
+ <% end %>
29
+
30
+ <% if wrap_in_viewport? %>
31
+ <%= content_tag :div, **viewport_computed_attrs do %>
32
+ <%= content_tag :div, **computed_attrs do %>
33
+ <%= toast_inner %>
34
+ <% end %>
35
+ <% end %>
36
+ <% else %>
37
+ <%= content_tag :div, **computed_attrs do %>
38
+ <%= toast_inner %>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ui
4
+ module Toast
5
+ # Brief notification with optional title, description, dismiss control, auto-dismiss,
6
+ # and optional fixed viewport wrapper (six screen positions).
7
+ class Component < Ui::BaseComponent
8
+ VARIANTS = %i[default success destructive warning].freeze
9
+
10
+ POSITIONS = %i[
11
+ top_left
12
+ top_center
13
+ top_right
14
+ bottom_left
15
+ bottom_center
16
+ bottom_right
17
+ ].freeze
18
+
19
+ renders_one :title
20
+ renders_one :description
21
+
22
+ attr_reader :variant, :dismissible, :duration_ms, :position, :viewport_html_attrs, :html_attrs
23
+
24
+ # rubocop:disable Metrics/ParameterLists -- explicit keywords match ViewComponent / ERB call style
25
+ def initialize(
26
+ variant: :default,
27
+ dismissible: true,
28
+ duration_ms: nil,
29
+ position: nil,
30
+ viewport_html_attrs: {},
31
+ **html_attrs
32
+ )
33
+ @variant = variant.to_sym
34
+ @dismissible = dismissible
35
+ @duration_ms = duration_ms&.to_i
36
+ @position = position.nil? ? nil : normalize_position(position)
37
+ @viewport_html_attrs = viewport_html_attrs
38
+ @html_attrs = html_attrs
39
+ super()
40
+ end
41
+ # rubocop:enable Metrics/ParameterLists
42
+
43
+ def show_icon?
44
+ variant != :default
45
+ end
46
+
47
+ def wrap_in_viewport?
48
+ !position.nil?
49
+ end
50
+
51
+ def needs_stimulus?
52
+ dismissible || duration_ms_positive?
53
+ end
54
+
55
+ def duration_ms_positive?
56
+ !duration_ms.nil? && duration_ms.positive?
57
+ end
58
+
59
+ private
60
+
61
+ def viewport_computed_attrs
62
+ modifier = "ui-toast-viewport--#{position.to_s.tr("_", "-")}"
63
+ defaults = {
64
+ class: class_names("ui-toast-viewport", modifier),
65
+ role: "region",
66
+ "aria-label": "Notifications",
67
+ "aria-live": "polite"
68
+ }
69
+ merge_attrs(defaults, viewport_html_attrs)
70
+ end
71
+
72
+ def normalize_position(value)
73
+ sym = value.to_sym
74
+ POSITIONS.include?(sym) ? sym : :bottom_right
75
+ end
76
+
77
+ def computed_attrs
78
+ attrs = merge_attrs(toast_root_defaults, html_attrs).dup
79
+ attrs[:data] = merged_stimulus_data(attrs[:data]) if needs_stimulus?
80
+ attrs
81
+ end
82
+
83
+ def toast_root_defaults
84
+ destructive = variant == :destructive
85
+ {
86
+ class: class_names("ui-toast", "ui-toast--#{variant}"),
87
+ role: destructive ? "alert" : "status",
88
+ "aria-live": destructive ? "assertive" : "polite",
89
+ "aria-atomic": "true"
90
+ }
91
+ end
92
+
93
+ def merged_stimulus_data(existing)
94
+ d = (existing || {}).dup
95
+ ctrls = [d[:controller], "ui--toast"].compact.join(" ").split(/\s+/).uniq.join(" ")
96
+ d[:controller] = ctrls
97
+ d[:"ui--toast-duration-ms-value"] = duration_ms if duration_ms_positive?
98
+ d
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,96 @@
1
+ description: >-
2
+ Short, dismissible notifications with optional auto-dismiss. Use the position keyword argument
3
+ for a fixed corner or center, or wrap several in .ui-toast-viewport. Stimulus ui--toast handles dismiss and duration.
4
+
5
+ sections:
6
+ - title: Variants
7
+ examples:
8
+ - title: Default
9
+ code: |
10
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
11
+ <%= render Ui::Toast::Component.new do |t| %>
12
+ <% t.with_title { "Saved" } %>
13
+ <% t.with_description { "Your changes were stored." } %>
14
+ <% end %>
15
+ </div>
16
+
17
+ - title: Success
18
+ code: |
19
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
20
+ <%= render Ui::Toast::Component.new(variant: :success) do |t| %>
21
+ <% t.with_title { "Profile updated" } %>
22
+ <% t.with_description { "We synced your settings." } %>
23
+ <% end %>
24
+ </div>
25
+
26
+ - title: Warning
27
+ code: |
28
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
29
+ <%= render Ui::Toast::Component.new(variant: :warning) do |t| %>
30
+ <% t.with_title { "Rate limit" } %>
31
+ <% t.with_description { "Try again in a few minutes." } %>
32
+ <% end %>
33
+ </div>
34
+
35
+ - title: Destructive
36
+ code: |
37
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
38
+ <%= render Ui::Toast::Component.new(variant: :destructive) do |t| %>
39
+ <% t.with_title { "Payment failed" } %>
40
+ <% t.with_description { "Check your card details and retry." } %>
41
+ <% end %>
42
+ </div>
43
+
44
+ - title: Behavior
45
+ examples:
46
+ - title: Without dismiss button
47
+ code: |
48
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
49
+ <%= render Ui::Toast::Component.new(dismissible: false) do |t| %>
50
+ <% t.with_description { "This toast stays until the page changes." } %>
51
+ <% end %>
52
+ </div>
53
+
54
+ - title: Auto-dismiss (5s) — requires ui--toast Stimulus
55
+ code: |
56
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
57
+ <%= render Ui::Toast::Component.new(duration_ms: 5000, dismissible: true) do |t| %>
58
+ <% t.with_title { "Sent" } %>
59
+ <% t.with_description { "This message removes itself after five seconds." } %>
60
+ <% end %>
61
+ </div>
62
+
63
+ - title: Message only
64
+ code: |
65
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
66
+ <%= render Ui::Toast::Component.new do |t| %>
67
+ <% t.with_description { "Copied to clipboard." } %>
68
+ <% end %>
69
+ </div>
70
+
71
+ - title: Viewport positions
72
+ examples:
73
+ - title: Top center
74
+ code: |
75
+ <%= render Ui::Toast::Component.new(variant: :success, position: :top_center, viewport_html_attrs: { class: "ui-toast-viewport--inline" }) do |t| %>
76
+ <% t.with_title { "Top center" } %>
77
+ <% t.with_description { "position: :top_center wraps a fixed viewport." } %>
78
+ <% end %>
79
+
80
+ - title: Bottom left
81
+ code: |
82
+ <%= render Ui::Toast::Component.new(variant: :warning, position: :bottom_left, viewport_html_attrs: { class: "ui-toast-viewport--inline" }) do |t| %>
83
+ <% t.with_title { "Bottom left" } %>
84
+ <% t.with_description { "position: :bottom_left adds the viewport wrapper." } %>
85
+ <% end %>
86
+
87
+ - title: Custom attributes
88
+ examples:
89
+ - title: Extra classes
90
+ code: |
91
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
92
+ <%= render Ui::Toast::Component.new(class: "my-toast", data: { testid: "notice-toast" }) do |t| %>
93
+ <% t.with_title { "Heads up" } %>
94
+ <% t.with_description { "You can pass data-* and class like any component." } %>
95
+ <% end %>
96
+ </div>
@@ -0,0 +1,241 @@
1
+ /* ============================================
2
+ * UikitRails — Toast
3
+ *
4
+ * Variants: default, success, destructive, warning
5
+ * Stack with .ui-toast-viewport (fixed bottom-right by default).
6
+ * Positions: .ui-toast-viewport--top-left | top-center | top-right |
7
+ * bottom-left | bottom-center | bottom-right
8
+ * Inline: .ui-toast-viewport--inline (flow layout in page content, not fixed)
9
+ * ============================================ */
10
+
11
+ .ui-toast-viewport {
12
+ position: fixed;
13
+ z-index: 100;
14
+ display: flex;
15
+ gap: 0.5rem;
16
+ width: min(100% - 2rem, 22rem);
17
+ max-height: min(70vh, 24rem);
18
+ overflow-y: auto;
19
+ overflow-x: hidden;
20
+ pointer-events: none;
21
+ }
22
+
23
+ /* Default anchor: bottom-right */
24
+ .ui-toast-viewport:not([class*="ui-toast-viewport--"]) {
25
+ right: 1rem;
26
+ bottom: 1rem;
27
+ flex-direction: column-reverse;
28
+ justify-content: flex-end;
29
+ }
30
+
31
+ .ui-toast-viewport--top-left {
32
+ top: 1rem;
33
+ left: 1rem;
34
+ right: auto;
35
+ bottom: auto;
36
+ flex-direction: column;
37
+ justify-content: flex-start;
38
+ }
39
+
40
+ .ui-toast-viewport--top-center {
41
+ top: 1rem;
42
+ left: 50%;
43
+ right: auto;
44
+ bottom: auto;
45
+ transform: translateX(-50%);
46
+ flex-direction: column;
47
+ justify-content: flex-start;
48
+ }
49
+
50
+ .ui-toast-viewport--top-right {
51
+ top: 1rem;
52
+ right: 1rem;
53
+ left: auto;
54
+ bottom: auto;
55
+ flex-direction: column;
56
+ justify-content: flex-start;
57
+ }
58
+
59
+ .ui-toast-viewport--bottom-left {
60
+ bottom: 1rem;
61
+ left: 1rem;
62
+ right: auto;
63
+ top: auto;
64
+ flex-direction: column-reverse;
65
+ justify-content: flex-end;
66
+ }
67
+
68
+ .ui-toast-viewport--bottom-center {
69
+ bottom: 1rem;
70
+ left: 50%;
71
+ right: auto;
72
+ top: auto;
73
+ transform: translateX(-50%);
74
+ flex-direction: column-reverse;
75
+ justify-content: flex-end;
76
+ }
77
+
78
+ .ui-toast-viewport--bottom-right {
79
+ bottom: 1rem;
80
+ right: 1rem;
81
+ left: auto;
82
+ top: auto;
83
+ flex-direction: column-reverse;
84
+ justify-content: flex-end;
85
+ }
86
+
87
+ /* Flow layout in a normal column (styleguide, docs) — disables fixed anchoring */
88
+ .ui-toast-viewport.ui-toast-viewport--inline {
89
+ position: relative;
90
+ inset: auto;
91
+ left: auto;
92
+ right: auto;
93
+ top: auto;
94
+ bottom: auto;
95
+ transform: none;
96
+ width: 100%;
97
+ max-height: none;
98
+ pointer-events: auto;
99
+ }
100
+
101
+ .ui-toast-viewport .ui-toast {
102
+ pointer-events: auto;
103
+ }
104
+
105
+ .ui-toast {
106
+ display: flex;
107
+ align-items: flex-start;
108
+ gap: 0.75rem;
109
+ width: 100%;
110
+ padding: 0.875rem 1rem;
111
+ font-family: var(--ui-font-family);
112
+ font-size: var(--ui-font-size-sm);
113
+ line-height: 1.45;
114
+ color: var(--ui-foreground);
115
+ background-color: var(--ui-background);
116
+ border: 1px solid var(--ui-border);
117
+ border-radius: var(--ui-radius);
118
+ box-shadow: 0 10px 15px -3px rgb(15 23 42 / 0.08), 0 4px 6px -4px rgb(15 23 42 / 0.06);
119
+ transition:
120
+ opacity 0.2s ease,
121
+ transform 0.2s ease;
122
+ }
123
+
124
+ .ui-toast--leaving {
125
+ opacity: 0;
126
+ transform: translateY(0.5rem) scale(0.98);
127
+ }
128
+
129
+ .ui-toast-viewport--top-left .ui-toast--leaving,
130
+ .ui-toast-viewport--top-center .ui-toast--leaving,
131
+ .ui-toast-viewport--top-right .ui-toast--leaving {
132
+ transform: translateY(-0.5rem) scale(0.98);
133
+ }
134
+
135
+ .ui-toast__icon {
136
+ flex-shrink: 0;
137
+ margin-top: 0.05rem;
138
+ color: var(--ui-muted-foreground);
139
+ }
140
+
141
+ .ui-toast__main {
142
+ flex: 1;
143
+ min-width: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 0.2rem;
147
+ }
148
+
149
+ .ui-toast__title {
150
+ font-weight: 600;
151
+ letter-spacing: -0.01em;
152
+ }
153
+
154
+ .ui-toast__description {
155
+ color: var(--ui-muted-foreground);
156
+ font-size: var(--ui-font-size-sm);
157
+ }
158
+
159
+ .ui-toast__close {
160
+ flex-shrink: 0;
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ width: 1.75rem;
165
+ height: 1.75rem;
166
+ margin: -0.2rem -0.35rem -0.2rem 0;
167
+ padding: 0;
168
+ border: none;
169
+ border-radius: calc(var(--ui-radius) - 2px);
170
+ background: transparent;
171
+ color: var(--ui-muted-foreground);
172
+ cursor: pointer;
173
+ transition:
174
+ background var(--ui-transition-speed) ease,
175
+ color var(--ui-transition-speed) ease;
176
+ }
177
+
178
+ .ui-toast__close:hover {
179
+ background: var(--ui-muted);
180
+ color: var(--ui-foreground);
181
+ }
182
+
183
+ .ui-toast__close:focus-visible {
184
+ outline: 2px solid var(--ui-ring);
185
+ outline-offset: 2px;
186
+ }
187
+
188
+ /* --- Variants --- */
189
+
190
+ .ui-toast--success {
191
+ border-left: 4px solid #22c55e;
192
+ }
193
+
194
+ .ui-toast--success .ui-toast__icon {
195
+ color: #16a34a;
196
+ }
197
+
198
+ .ui-toast--warning {
199
+ border-left: 4px solid #f59e0b;
200
+ }
201
+
202
+ .ui-toast--warning .ui-toast__icon {
203
+ color: #d97706;
204
+ }
205
+
206
+ .ui-toast--destructive {
207
+ border-color: var(--ui-border);
208
+ border-left: 4px solid var(--ui-destructive);
209
+ }
210
+
211
+ .ui-toast--destructive .ui-toast__icon {
212
+ color: var(--ui-destructive);
213
+ }
214
+
215
+ .ui-toast--destructive .ui-toast__description {
216
+ color: var(--ui-foreground);
217
+ opacity: 0.88;
218
+ }
219
+
220
+ .dark .ui-toast {
221
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.35), 0 4px 6px -4px rgb(0 0 0 / 0.25);
222
+ }
223
+
224
+ .dark .ui-toast--success .ui-toast__icon {
225
+ color: #4ade80;
226
+ }
227
+
228
+ .dark .ui-toast--warning .ui-toast__icon {
229
+ color: #fbbf24;
230
+ }
231
+
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .ui-toast {
234
+ transition: none;
235
+ }
236
+
237
+ .ui-toast--leaving {
238
+ opacity: 0;
239
+ transform: none;
240
+ }
241
+ }
@@ -0,0 +1,38 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { durationMs: Number }
5
+
6
+ connect() {
7
+ if (this.hasDurationMsValue && this.durationMsValue > 0) {
8
+ this.timeoutId = window.setTimeout(() => this.dismiss(), this.durationMsValue)
9
+ }
10
+ }
11
+
12
+ disconnect() {
13
+ if (this.timeoutId) {
14
+ window.clearTimeout(this.timeoutId)
15
+ this.timeoutId = null
16
+ }
17
+ }
18
+
19
+ dismiss() {
20
+ if (this.timeoutId) {
21
+ window.clearTimeout(this.timeoutId)
22
+ this.timeoutId = null
23
+ }
24
+
25
+ const el = this.element
26
+ el.classList.add("ui-toast--leaving")
27
+
28
+ let done = false
29
+ const finish = () => {
30
+ if (done) return
31
+ done = true
32
+ el.remove()
33
+ }
34
+
35
+ el.addEventListener("transitionend", finish, { once: true })
36
+ window.setTimeout(finish, 320)
37
+ }
38
+ }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UikitRails
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/uikit_rails.rb CHANGED
@@ -33,6 +33,7 @@ module UikitRails
33
33
  table
34
34
  tabs
35
35
  textarea
36
+ toast
36
37
  toggle
37
38
  tooltip
38
39
  ].freeze
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- uikit_rails (0.1.2)
4
+ uikit_rails (0.1.3)
5
5
  railties (>= 7.0)
6
6
  view_component (>= 3.0)
7
7
 
@@ -570,7 +570,7 @@ CHECKSUMS
570
570
  tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
571
571
  turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
572
572
  tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
573
- uikit_rails (0.1.2)
573
+ uikit_rails (0.1.3)
574
574
  unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
575
575
  unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
576
576
  uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
@@ -0,0 +1,241 @@
1
+ /* ============================================
2
+ * UikitRails — Toast
3
+ *
4
+ * Variants: default, success, destructive, warning
5
+ * Stack with .ui-toast-viewport (fixed bottom-right by default).
6
+ * Positions: .ui-toast-viewport--top-left | top-center | top-right |
7
+ * bottom-left | bottom-center | bottom-right
8
+ * Inline: .ui-toast-viewport--inline (flow layout in page content, not fixed)
9
+ * ============================================ */
10
+
11
+ .ui-toast-viewport {
12
+ position: fixed;
13
+ z-index: 100;
14
+ display: flex;
15
+ gap: 0.5rem;
16
+ width: min(100% - 2rem, 22rem);
17
+ max-height: min(70vh, 24rem);
18
+ overflow-y: auto;
19
+ overflow-x: hidden;
20
+ pointer-events: none;
21
+ }
22
+
23
+ /* Default anchor: bottom-right */
24
+ .ui-toast-viewport:not([class*="ui-toast-viewport--"]) {
25
+ right: 1rem;
26
+ bottom: 1rem;
27
+ flex-direction: column-reverse;
28
+ justify-content: flex-end;
29
+ }
30
+
31
+ .ui-toast-viewport--top-left {
32
+ top: 1rem;
33
+ left: 1rem;
34
+ right: auto;
35
+ bottom: auto;
36
+ flex-direction: column;
37
+ justify-content: flex-start;
38
+ }
39
+
40
+ .ui-toast-viewport--top-center {
41
+ top: 1rem;
42
+ left: 50%;
43
+ right: auto;
44
+ bottom: auto;
45
+ transform: translateX(-50%);
46
+ flex-direction: column;
47
+ justify-content: flex-start;
48
+ }
49
+
50
+ .ui-toast-viewport--top-right {
51
+ top: 1rem;
52
+ right: 1rem;
53
+ left: auto;
54
+ bottom: auto;
55
+ flex-direction: column;
56
+ justify-content: flex-start;
57
+ }
58
+
59
+ .ui-toast-viewport--bottom-left {
60
+ bottom: 1rem;
61
+ left: 1rem;
62
+ right: auto;
63
+ top: auto;
64
+ flex-direction: column-reverse;
65
+ justify-content: flex-end;
66
+ }
67
+
68
+ .ui-toast-viewport--bottom-center {
69
+ bottom: 1rem;
70
+ left: 50%;
71
+ right: auto;
72
+ top: auto;
73
+ transform: translateX(-50%);
74
+ flex-direction: column-reverse;
75
+ justify-content: flex-end;
76
+ }
77
+
78
+ .ui-toast-viewport--bottom-right {
79
+ bottom: 1rem;
80
+ right: 1rem;
81
+ left: auto;
82
+ top: auto;
83
+ flex-direction: column-reverse;
84
+ justify-content: flex-end;
85
+ }
86
+
87
+ /* Flow layout in a normal column (styleguide, docs) — disables fixed anchoring */
88
+ .ui-toast-viewport.ui-toast-viewport--inline {
89
+ position: relative;
90
+ inset: auto;
91
+ left: auto;
92
+ right: auto;
93
+ top: auto;
94
+ bottom: auto;
95
+ transform: none;
96
+ width: 100%;
97
+ max-height: none;
98
+ pointer-events: auto;
99
+ }
100
+
101
+ .ui-toast-viewport .ui-toast {
102
+ pointer-events: auto;
103
+ }
104
+
105
+ .ui-toast {
106
+ display: flex;
107
+ align-items: flex-start;
108
+ gap: 0.75rem;
109
+ width: 100%;
110
+ padding: 0.875rem 1rem;
111
+ font-family: var(--ui-font-family);
112
+ font-size: var(--ui-font-size-sm);
113
+ line-height: 1.45;
114
+ color: var(--ui-foreground);
115
+ background-color: var(--ui-background);
116
+ border: 1px solid var(--ui-border);
117
+ border-radius: var(--ui-radius);
118
+ box-shadow: 0 10px 15px -3px rgb(15 23 42 / 0.08), 0 4px 6px -4px rgb(15 23 42 / 0.06);
119
+ transition:
120
+ opacity 0.2s ease,
121
+ transform 0.2s ease;
122
+ }
123
+
124
+ .ui-toast--leaving {
125
+ opacity: 0;
126
+ transform: translateY(0.5rem) scale(0.98);
127
+ }
128
+
129
+ .ui-toast-viewport--top-left .ui-toast--leaving,
130
+ .ui-toast-viewport--top-center .ui-toast--leaving,
131
+ .ui-toast-viewport--top-right .ui-toast--leaving {
132
+ transform: translateY(-0.5rem) scale(0.98);
133
+ }
134
+
135
+ .ui-toast__icon {
136
+ flex-shrink: 0;
137
+ margin-top: 0.05rem;
138
+ color: var(--ui-muted-foreground);
139
+ }
140
+
141
+ .ui-toast__main {
142
+ flex: 1;
143
+ min-width: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 0.2rem;
147
+ }
148
+
149
+ .ui-toast__title {
150
+ font-weight: 600;
151
+ letter-spacing: -0.01em;
152
+ }
153
+
154
+ .ui-toast__description {
155
+ color: var(--ui-muted-foreground);
156
+ font-size: var(--ui-font-size-sm);
157
+ }
158
+
159
+ .ui-toast__close {
160
+ flex-shrink: 0;
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ width: 1.75rem;
165
+ height: 1.75rem;
166
+ margin: -0.2rem -0.35rem -0.2rem 0;
167
+ padding: 0;
168
+ border: none;
169
+ border-radius: calc(var(--ui-radius) - 2px);
170
+ background: transparent;
171
+ color: var(--ui-muted-foreground);
172
+ cursor: pointer;
173
+ transition:
174
+ background var(--ui-transition-speed) ease,
175
+ color var(--ui-transition-speed) ease;
176
+ }
177
+
178
+ .ui-toast__close:hover {
179
+ background: var(--ui-muted);
180
+ color: var(--ui-foreground);
181
+ }
182
+
183
+ .ui-toast__close:focus-visible {
184
+ outline: 2px solid var(--ui-ring);
185
+ outline-offset: 2px;
186
+ }
187
+
188
+ /* --- Variants --- */
189
+
190
+ .ui-toast--success {
191
+ border-left: 4px solid #22c55e;
192
+ }
193
+
194
+ .ui-toast--success .ui-toast__icon {
195
+ color: #16a34a;
196
+ }
197
+
198
+ .ui-toast--warning {
199
+ border-left: 4px solid #f59e0b;
200
+ }
201
+
202
+ .ui-toast--warning .ui-toast__icon {
203
+ color: #d97706;
204
+ }
205
+
206
+ .ui-toast--destructive {
207
+ border-color: var(--ui-border);
208
+ border-left: 4px solid var(--ui-destructive);
209
+ }
210
+
211
+ .ui-toast--destructive .ui-toast__icon {
212
+ color: var(--ui-destructive);
213
+ }
214
+
215
+ .ui-toast--destructive .ui-toast__description {
216
+ color: var(--ui-foreground);
217
+ opacity: 0.88;
218
+ }
219
+
220
+ .dark .ui-toast {
221
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.35), 0 4px 6px -4px rgb(0 0 0 / 0.25);
222
+ }
223
+
224
+ .dark .ui-toast--success .ui-toast__icon {
225
+ color: #4ade80;
226
+ }
227
+
228
+ .dark .ui-toast--warning .ui-toast__icon {
229
+ color: #fbbf24;
230
+ }
231
+
232
+ @media (prefers-reduced-motion: reduce) {
233
+ .ui-toast {
234
+ transition: none;
235
+ }
236
+
237
+ .ui-toast--leaving {
238
+ opacity: 0;
239
+ transform: none;
240
+ }
241
+ }
@@ -0,0 +1,59 @@
1
+ Basic toast:
2
+
3
+ <%= render Ui::Toast::Component.new do |toast| %>
4
+ <% toast.with_title do %>Done<% end %>
5
+ <% toast.with_description do %>Operation completed.<% end %>
6
+ <% end %>
7
+
8
+ Variants (default, success, destructive, warning):
9
+
10
+ <%= render Ui::Toast::Component.new(variant: :success) do |toast| %>
11
+ <% toast.with_description do %>Saved.<% end %>
12
+ <% end %>
13
+
14
+ Without dismiss button:
15
+
16
+ <%= render Ui::Toast::Component.new(dismissible: false) do |toast| %>
17
+ <% toast.with_description do %>Processing…<% end %>
18
+ <% end %>
19
+
20
+ With auto-dismiss (duration_ms; requires ui--toast Stimulus):
21
+
22
+ <%= render Ui::Toast::Component.new(duration_ms: 4000) do |toast| %>
23
+ <% toast.with_description do %>Session expires soon.<% end %>
24
+ <% end %>
25
+
26
+ Stack multiple toasts — wrap in a viewport (see toast.css), omit position on each toast:
27
+
28
+ <div class="ui-toast-viewport ui-toast-viewport--bottom-right" aria-live="polite" aria-label="Notifications">
29
+ <%= render Ui::Toast::Component.new(variant: :success) do |toast| %>
30
+ <% toast.with_description do %>First<% end %>
31
+ <% end %>
32
+ <%= render Ui::Toast::Component.new do |toast| %>
33
+ <% toast.with_description do %>Second<% end %>
34
+ <% end %>
35
+ </div>
36
+
37
+ Single toast with fixed position (wraps in .ui-toast-viewport for you):
38
+
39
+ position: :top_left, :top_center, :top_right, :bottom_left, :bottom_center, :bottom_right
40
+
41
+ <%= render Ui::Toast::Component.new(position: :top_center) do |toast| %>
42
+ <% toast.with_description do %>Centered along the top edge.<% end %>
43
+ <% end %>
44
+
45
+ For in-flow previews (not fixed to the window), add ui-toast-viewport--inline on the viewport
46
+ or viewport_html_attrs: { class: "ui-toast-viewport--inline" } when using position:.
47
+
48
+ Slots:
49
+ title — Optional heading
50
+ description — Optional supporting text
51
+ content — Default slot for extra markup
52
+
53
+ Close button uses data-action="click->ui--toast#dismiss" (removes the node after a short animation).
54
+
55
+ With custom attributes:
56
+
57
+ <%= render Ui::Toast::Component.new(class: "extra", data: { testid: "notice" }) do |toast| %>
58
+ <% toast.with_title do %>Heads up<% end %>
59
+ <% end %>
@@ -0,0 +1,40 @@
1
+ <% toast_inner = capture do %>
2
+ <% if show_icon? %>
3
+ <div class="ui-toast__icon" aria-hidden="true">
4
+ <% case variant
5
+ when :success %>
6
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
7
+ <% when :destructive %>
8
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
9
+ <% when :warning %>
10
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></svg>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
14
+ <div class="ui-toast__main">
15
+ <% if title? %>
16
+ <div class="ui-toast__title"><%= title %></div>
17
+ <% end %>
18
+ <% if description? %>
19
+ <div class="ui-toast__description"><%= description %></div>
20
+ <% end %>
21
+ <%= content %>
22
+ </div>
23
+ <% if dismissible %>
24
+ <button type="button" class="ui-toast__close" data-action="click->ui--toast#dismiss" aria-label="Dismiss notification">
25
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
26
+ </button>
27
+ <% end %>
28
+ <% end %>
29
+
30
+ <% if wrap_in_viewport? %>
31
+ <%= content_tag :div, **viewport_computed_attrs do %>
32
+ <%= content_tag :div, **computed_attrs do %>
33
+ <%= toast_inner %>
34
+ <% end %>
35
+ <% end %>
36
+ <% else %>
37
+ <%= content_tag :div, **computed_attrs do %>
38
+ <%= toast_inner %>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ui
4
+ module Toast
5
+ # Brief notification with optional title, description, dismiss control, auto-dismiss,
6
+ # and optional fixed viewport wrapper (six screen positions).
7
+ class Component < Ui::BaseComponent
8
+ VARIANTS = %i[default success destructive warning].freeze
9
+
10
+ POSITIONS = %i[
11
+ top_left
12
+ top_center
13
+ top_right
14
+ bottom_left
15
+ bottom_center
16
+ bottom_right
17
+ ].freeze
18
+
19
+ renders_one :title
20
+ renders_one :description
21
+
22
+ attr_reader :variant, :dismissible, :duration_ms, :position, :viewport_html_attrs, :html_attrs
23
+
24
+ # rubocop:disable Metrics/ParameterLists -- explicit keywords match ViewComponent / ERB call style
25
+ def initialize(
26
+ variant: :default,
27
+ dismissible: true,
28
+ duration_ms: nil,
29
+ position: nil,
30
+ viewport_html_attrs: {},
31
+ **html_attrs
32
+ )
33
+ @variant = variant.to_sym
34
+ @dismissible = dismissible
35
+ @duration_ms = duration_ms&.to_i
36
+ @position = position.nil? ? nil : normalize_position(position)
37
+ @viewport_html_attrs = viewport_html_attrs
38
+ @html_attrs = html_attrs
39
+ super()
40
+ end
41
+ # rubocop:enable Metrics/ParameterLists
42
+
43
+ def show_icon?
44
+ variant != :default
45
+ end
46
+
47
+ def wrap_in_viewport?
48
+ !position.nil?
49
+ end
50
+
51
+ def needs_stimulus?
52
+ dismissible || duration_ms_positive?
53
+ end
54
+
55
+ def duration_ms_positive?
56
+ !duration_ms.nil? && duration_ms.positive?
57
+ end
58
+
59
+ private
60
+
61
+ def viewport_computed_attrs
62
+ modifier = "ui-toast-viewport--#{position.to_s.tr("_", "-")}"
63
+ defaults = {
64
+ class: class_names("ui-toast-viewport", modifier),
65
+ role: "region",
66
+ "aria-label": "Notifications",
67
+ "aria-live": "polite"
68
+ }
69
+ merge_attrs(defaults, viewport_html_attrs)
70
+ end
71
+
72
+ def normalize_position(value)
73
+ sym = value.to_sym
74
+ POSITIONS.include?(sym) ? sym : :bottom_right
75
+ end
76
+
77
+ def computed_attrs
78
+ attrs = merge_attrs(toast_root_defaults, html_attrs).dup
79
+ attrs[:data] = merged_stimulus_data(attrs[:data]) if needs_stimulus?
80
+ attrs
81
+ end
82
+
83
+ def toast_root_defaults
84
+ destructive = variant == :destructive
85
+ {
86
+ class: class_names("ui-toast", "ui-toast--#{variant}"),
87
+ role: destructive ? "alert" : "status",
88
+ "aria-live": destructive ? "assertive" : "polite",
89
+ "aria-atomic": "true"
90
+ }
91
+ end
92
+
93
+ def merged_stimulus_data(existing)
94
+ d = (existing || {}).dup
95
+ ctrls = [d[:controller], "ui--toast"].compact.join(" ").split(/\s+/).uniq.join(" ")
96
+ d[:controller] = ctrls
97
+ d[:"ui--toast-duration-ms-value"] = duration_ms if duration_ms_positive?
98
+ d
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,96 @@
1
+ description: >-
2
+ Short, dismissible notifications with optional auto-dismiss. Use the position keyword argument
3
+ for a fixed corner or center, or wrap several in .ui-toast-viewport. Stimulus ui--toast handles dismiss and duration.
4
+
5
+ sections:
6
+ - title: Variants
7
+ examples:
8
+ - title: Default
9
+ code: |
10
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
11
+ <%= render Ui::Toast::Component.new do |t| %>
12
+ <% t.with_title { "Saved" } %>
13
+ <% t.with_description { "Your changes were stored." } %>
14
+ <% end %>
15
+ </div>
16
+
17
+ - title: Success
18
+ code: |
19
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
20
+ <%= render Ui::Toast::Component.new(variant: :success) do |t| %>
21
+ <% t.with_title { "Profile updated" } %>
22
+ <% t.with_description { "We synced your settings." } %>
23
+ <% end %>
24
+ </div>
25
+
26
+ - title: Warning
27
+ code: |
28
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
29
+ <%= render Ui::Toast::Component.new(variant: :warning) do |t| %>
30
+ <% t.with_title { "Rate limit" } %>
31
+ <% t.with_description { "Try again in a few minutes." } %>
32
+ <% end %>
33
+ </div>
34
+
35
+ - title: Destructive
36
+ code: |
37
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
38
+ <%= render Ui::Toast::Component.new(variant: :destructive) do |t| %>
39
+ <% t.with_title { "Payment failed" } %>
40
+ <% t.with_description { "Check your card details and retry." } %>
41
+ <% end %>
42
+ </div>
43
+
44
+ - title: Behavior
45
+ examples:
46
+ - title: Without dismiss button
47
+ code: |
48
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
49
+ <%= render Ui::Toast::Component.new(dismissible: false) do |t| %>
50
+ <% t.with_description { "This toast stays until the page changes." } %>
51
+ <% end %>
52
+ </div>
53
+
54
+ - title: Auto-dismiss (5s) — requires ui--toast Stimulus
55
+ code: |
56
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
57
+ <%= render Ui::Toast::Component.new(duration_ms: 5000, dismissible: true) do |t| %>
58
+ <% t.with_title { "Sent" } %>
59
+ <% t.with_description { "This message removes itself after five seconds." } %>
60
+ <% end %>
61
+ </div>
62
+
63
+ - title: Message only
64
+ code: |
65
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
66
+ <%= render Ui::Toast::Component.new do |t| %>
67
+ <% t.with_description { "Copied to clipboard." } %>
68
+ <% end %>
69
+ </div>
70
+
71
+ - title: Viewport positions
72
+ examples:
73
+ - title: Top center
74
+ code: |
75
+ <%= render Ui::Toast::Component.new(variant: :success, position: :top_center, viewport_html_attrs: { class: "ui-toast-viewport--inline" }) do |t| %>
76
+ <% t.with_title { "Top center" } %>
77
+ <% t.with_description { "position: :top_center wraps a fixed viewport." } %>
78
+ <% end %>
79
+
80
+ - title: Bottom left
81
+ code: |
82
+ <%= render Ui::Toast::Component.new(variant: :warning, position: :bottom_left, viewport_html_attrs: { class: "ui-toast-viewport--inline" }) do |t| %>
83
+ <% t.with_title { "Bottom left" } %>
84
+ <% t.with_description { "position: :bottom_left adds the viewport wrapper." } %>
85
+ <% end %>
86
+
87
+ - title: Custom attributes
88
+ examples:
89
+ - title: Extra classes
90
+ code: |
91
+ <div class="ui-toast-viewport ui-toast-viewport--inline">
92
+ <%= render Ui::Toast::Component.new(class: "my-toast", data: { testid: "notice-toast" }) do |t| %>
93
+ <% t.with_title { "Heads up" } %>
94
+ <% t.with_description { "You can pass data-* and class like any component." } %>
95
+ <% end %>
96
+ </div>
@@ -0,0 +1,38 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { durationMs: Number }
5
+
6
+ connect() {
7
+ if (this.hasDurationMsValue && this.durationMsValue > 0) {
8
+ this.timeoutId = window.setTimeout(() => this.dismiss(), this.durationMsValue)
9
+ }
10
+ }
11
+
12
+ disconnect() {
13
+ if (this.timeoutId) {
14
+ window.clearTimeout(this.timeoutId)
15
+ this.timeoutId = null
16
+ }
17
+ }
18
+
19
+ dismiss() {
20
+ if (this.timeoutId) {
21
+ window.clearTimeout(this.timeoutId)
22
+ this.timeoutId = null
23
+ }
24
+
25
+ const el = this.element
26
+ el.classList.add("ui-toast--leaving")
27
+
28
+ let done = false
29
+ const finish = () => {
30
+ if (done) return
31
+ done = true
32
+ el.remove()
33
+ }
34
+
35
+ el.addEventListener("transitionend", finish, { once: true })
36
+ window.setTimeout(finish, 320)
37
+ }
38
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uikit_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manh Kha
@@ -180,6 +180,11 @@ files:
180
180
  - lib/uikit_rails/templates/components/textarea/component.rb
181
181
  - lib/uikit_rails/templates/components/textarea/preview.yml
182
182
  - lib/uikit_rails/templates/components/textarea/textarea.css
183
+ - lib/uikit_rails/templates/components/toast/USAGE
184
+ - lib/uikit_rails/templates/components/toast/component.html.erb
185
+ - lib/uikit_rails/templates/components/toast/component.rb
186
+ - lib/uikit_rails/templates/components/toast/preview.yml
187
+ - lib/uikit_rails/templates/components/toast/toast.css
183
188
  - lib/uikit_rails/templates/components/toggle/USAGE
184
189
  - lib/uikit_rails/templates/components/toggle/component.rb
185
190
  - lib/uikit_rails/templates/components/toggle/preview.yml
@@ -197,6 +202,7 @@ files:
197
202
  - lib/uikit_rails/templates/stimulus/popover_controller.js
198
203
  - lib/uikit_rails/templates/stimulus/sheet_controller.js
199
204
  - lib/uikit_rails/templates/stimulus/tabs_controller.js
205
+ - lib/uikit_rails/templates/stimulus/toast_controller.js
200
206
  - lib/uikit_rails/templates/stimulus/tooltip_controller.js
201
207
  - lib/uikit_rails/version.rb
202
208
  - sig/uikit_rails.rbs
@@ -250,6 +256,7 @@ files:
250
256
  - test_app/app/assets/stylesheets/ui/table.css
251
257
  - test_app/app/assets/stylesheets/ui/tabs.css
252
258
  - test_app/app/assets/stylesheets/ui/textarea.css
259
+ - test_app/app/assets/stylesheets/ui/toast.css
253
260
  - test_app/app/assets/stylesheets/ui/toggle.css
254
261
  - test_app/app/assets/stylesheets/ui/tooltip.css
255
262
  - test_app/app/assets/stylesheets/uikit_rails.css
@@ -327,6 +334,10 @@ files:
327
334
  - test_app/app/components/ui/tabs/preview.yml
328
335
  - test_app/app/components/ui/textarea/component.rb
329
336
  - test_app/app/components/ui/textarea/preview.yml
337
+ - test_app/app/components/ui/toast/USAGE
338
+ - test_app/app/components/ui/toast/component.html.erb
339
+ - test_app/app/components/ui/toast/component.rb
340
+ - test_app/app/components/ui/toast/preview.yml
330
341
  - test_app/app/components/ui/toggle/component.rb
331
342
  - test_app/app/components/ui/toggle/preview.yml
332
343
  - test_app/app/components/ui/tooltip/component.html.erb
@@ -347,6 +358,7 @@ files:
347
358
  - test_app/app/javascript/controllers/ui/popover_controller.js
348
359
  - test_app/app/javascript/controllers/ui/sheet_controller.js
349
360
  - test_app/app/javascript/controllers/ui/tabs_controller.js
361
+ - test_app/app/javascript/controllers/ui/toast_controller.js
350
362
  - test_app/app/javascript/controllers/ui/tooltip_controller.js
351
363
  - test_app/app/jobs/application_job.rb
352
364
  - test_app/app/mailers/application_mailer.rb