Skip to content

Commit 4a69ae2

Browse files
committed
[Documentation] Add close_button example + API Reference tables
- New 'Close Button' example box: clicking spawns a toast with X visible at top-right (top of stack inside item). - Toaster JS spawn now honors detail.closeButton: appends a top-right ghost X button and adds pr-10 to the cloned node. - API Reference section after About: - Toaster (Region) props: 12 entries with default + values + desc. - ToastItem props: 7 entries. - JS API options: 10 entries (callable as RubyUI.toast.* or via ruby-ui:toast CustomEvent detail). - Reusable inline props_table helper; uses RubyUI Table primitives.
1 parent fc6e71f commit 4a69ae2

4 files changed

Lines changed: 115 additions & 2 deletions

File tree

docs/app/javascript/controllers/ruby_ui/toaster_controller.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ export default class extends Controller {
121121
node.appendChild(btn)
122122
}
123123

124+
if (detail.closeButton) {
125+
const x = document.createElement("button")
126+
x.type = "button"
127+
x.dataset.slot = "close"
128+
x.dataset.action = "click->ruby-ui--toast#dismiss"
129+
x.setAttribute("aria-label", "Close toast")
130+
x.className = "absolute right-2 top-2 size-6 cursor-pointer rounded-md text-foreground/60 p-0 flex items-center justify-center transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
131+
x.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3.5"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg><span class="sr-only">Close</span>'
132+
node.classList.add("pr-10")
133+
node.appendChild(x)
134+
}
135+
124136
this._listEl.appendChild(node)
125137
return node.id
126138
}

docs/app/javascript/controllers/toast_demo_controller.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const PRESETS = {
88
error: { variant: "error", title: "Event has not been created" },
99
with_action: { variant: "default", title: "Event has been created", action: { label: "Undo" } },
1010
text_only: { variant: "default", title: "Event has been created" },
11+
close_button: { variant: "default", title: "Event has been created", description: "Close it manually with the X.", closeButton: true },
1112
}
1213

1314
export default class extends Controller {
@@ -29,7 +30,11 @@ export default class extends Controller {
2930
}
3031

3132
const preset = PRESETS[kind] || PRESETS.default
32-
const opts = { description: preset.description, action: preset.action }
33+
const opts = {
34+
description: preset.description,
35+
action: preset.action,
36+
closeButton: preset.closeButton,
37+
}
3338
const fn = t[preset.variant] || t
3439
fn(preset.title, opts)
3540
}

docs/app/views/docs/toast.rb

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class Views::Docs::Toast < Views::Base
1111
{key: "error", label: "Error", title: "Event has not been created"},
1212
{key: "with_action", label: "With Action", title: "Event has been created", action_label: "Undo"},
1313
{key: "promise", label: "Promise", title: nil},
14-
{key: "text_only", label: "Text Only", title: "Event has been created"}
14+
{key: "text_only", label: "Text Only", title: "Event has been created"},
15+
{key: "close_button", label: "Close Button", title: "Event has been created"}
1516
].freeze
1617

1718
POSITIONS = %w[top-left top-center top-right bottom-left bottom-center bottom-right].freeze
@@ -114,10 +115,93 @@ def view_template
114115
plain "."
115116
end
116117

118+
Heading(level: 2) { "API Reference" }
119+
120+
Heading(level: 3) { "Toaster (Region)" }
121+
props_table(REGION_PROPS)
122+
123+
Heading(level: 3) { "ToastItem" }
124+
props_table(ITEM_PROPS)
125+
126+
Heading(level: 3) { "JS API options" }
127+
p(class: "text-muted-foreground text-sm") do
128+
plain "Second argument to "
129+
code(class: "rounded bg-muted px-1.5 py-0.5 text-xs") { "RubyUI.toast.<variant>(message, options)" }
130+
plain " or "
131+
code(class: "rounded bg-muted px-1.5 py-0.5 text-xs") { "ruby-ui:toast" }
132+
plain " CustomEvent detail."
133+
end
134+
props_table(JS_OPTIONS)
135+
117136
render Docs::ComponentsTable.new(component_files(component))
118137
end
119138
end
120139

140+
REGION_PROPS = [
141+
{name: "position", default: ":bottom_right", values: ":top_left | :top_center | :top_right | :bottom_left | :bottom_center | :bottom_right", description: "Where the toaster mounts on the viewport."},
142+
{name: "expand", default: "false", values: "Boolean", description: "Always show items expanded (no stack peek)."},
143+
{name: "max", default: "3", values: "Integer", description: "Max visible toasts before oldest auto-evicts."},
144+
{name: "duration", default: "4000", values: "Integer (ms)", description: "Default lifetime per toast. Pass 0 or Infinity to disable auto-dismiss."},
145+
{name: "gap", default: "14", values: "Integer (px)", description: "Spacing between toasts when expanded."},
146+
{name: "offset", default: "24", values: "Integer (px)", description: "Distance from the viewport edge."},
147+
{name: "theme", default: ":system", values: ":system | :light | :dark", description: "Color scheme override."},
148+
{name: "rich_colors", default: "false", values: "Boolean", description: "Enable variant-tinted backgrounds."},
149+
{name: "close_button", default: "false", values: "Boolean", description: "Render an X button in every toast (top-right)."},
150+
{name: "hotkey", default: '%w[alt t]', values: "Array<String>", description: "Keyboard shortcut to focus the first toast."},
151+
{name: "dir", default: ":ltr", values: ":ltr | :rtl", description: "Text direction."},
152+
{name: "flash", default: "nil", values: "Hash | nil", description: "Pass `helpers.flash.to_h` to render Rails flash on initial load."}
153+
].freeze
154+
155+
ITEM_PROPS = [
156+
{name: "variant", default: ":default", values: ":default | :success | :error | :warning | :info | :loading", description: "Visual + a11y role + icon."},
157+
{name: "id", default: "nil", values: "String", description: "DOM id; auto-generated when not provided."},
158+
{name: "duration", default: "nil", values: "Integer | nil", description: "Override the Region default. nil inherits."},
159+
{name: "dismissible", default: "true", values: "Boolean", description: "Allow Escape, swipe, X, and force-dismiss to close."},
160+
{name: "invert", default: "false", values: "Boolean", description: "Invert background/foreground (light-on-dark in light theme)."},
161+
{name: "on_dismiss", default: "nil", values: "String", description: "Stimulus action descriptor fired when the user dismisses."},
162+
{name: "on_auto_close", default: "nil", values: "String", description: "Stimulus action descriptor fired when the timer expires."}
163+
].freeze
164+
165+
JS_OPTIONS = [
166+
{name: "title", default: "—", values: "String", description: "Headline text. Falls back to the first positional argument."},
167+
{name: "description", default: "—", values: "String", description: "Secondary line under the title."},
168+
{name: "duration", default: "(Region default)", values: "Number | Infinity", description: "ms before auto-dismiss. Infinity = sticky."},
169+
{name: "action", default: "—", values: "{ label, onClick }", description: "Primary action button rendered inside the toast."},
170+
{name: "cancel", default: "—", values: "{ label, onClick }", description: "Secondary dismiss button."},
171+
{name: "closeButton", default: "false", values: "Boolean", description: "Force an X close button on this toast."},
172+
{name: "position", default: "(Region default)", values: "String", description: "Per-toast position override (changes Region's data-position before append)."},
173+
{name: "id", default: "(auto)", values: "String", description: "Set a stable id (used by .dismiss(id) and .promise)."},
174+
{name: "dismissible", default: "true", values: "Boolean", description: "Disable Escape / swipe / dismiss-all for this toast."},
175+
{name: "className", default: "—", values: "String", description: "Extra classes appended to the rendered <li>."}
176+
].freeze
177+
178+
private
179+
180+
def props_table(rows)
181+
div(class: "border rounded-lg overflow-hidden") do
182+
Table do
183+
TableHeader do
184+
TableRow do
185+
TableHead { "Prop" }
186+
TableHead { "Default" }
187+
TableHead { "Values" }
188+
TableHead(class: "w-full") { "Description" }
189+
end
190+
end
191+
TableBody do
192+
rows.each do |r|
193+
TableRow do
194+
TableCell { InlineCode { r[:name] } }
195+
TableCell { InlineCode { r[:default] } }
196+
TableCell(class: "whitespace-normal") { InlineCode { r[:values] } }
197+
TableCell(class: "text-muted-foreground") { r[:description] }
198+
end
199+
end
200+
end
201+
end
202+
end
203+
end
204+
121205
private
122206

123207
def example_box(ex)

gem/lib/ruby_ui/toast/toaster_controller.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ export default class extends Controller {
121121
node.appendChild(btn)
122122
}
123123

124+
if (detail.closeButton) {
125+
const x = document.createElement("button")
126+
x.type = "button"
127+
x.dataset.slot = "close"
128+
x.dataset.action = "click->ruby-ui--toast#dismiss"
129+
x.setAttribute("aria-label", "Close toast")
130+
x.className = "absolute right-2 top-2 size-6 cursor-pointer rounded-md text-foreground/60 p-0 flex items-center justify-center transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
131+
x.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3.5"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg><span class="sr-only">Close</span>'
132+
node.classList.add("pr-10")
133+
node.appendChild(x)
134+
}
135+
124136
this._listEl.appendChild(node)
125137
return node.id
126138
}

0 commit comments

Comments
 (0)