Skip to content

Commit 1e265a5

Browse files
committed
Merge remote-tracking branch 'origin/main' into da/toggle
# Conflicts: # docs/app/components/shared/components_list.rb # docs/app/controllers/docs_controller.rb # docs/app/javascript/controllers/index.js # docs/config/routes.rb
2 parents 49a1103 + 1c1666a commit 1e265a5

42 files changed

Lines changed: 2160 additions & 16 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/.devcontainer/compose.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ services:
77
dockerfile: .devcontainer/Dockerfile
88

99
volumes:
10-
- ../../web:/workspaces/web:cached
10+
- ../..:/workspaces/ruby_ui:cached
11+
working_dir: /workspaces/ruby_ui/docs
12+
ports:
13+
- "3001:3000"
1114

1215
# Overrides default command so things don't shut down after the process ends.
1316
command: sleep infinity

docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
# Ignore bundler config.
88
/.bundle
9+
/vendor/bundle
910

1011
# Ignore all logfiles and tempfiles.
1112
/log/*

docs/app/components/shared/components_list.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def components
5050
{name: "Tabs", path: docs_tabs_path},
5151
{name: "Textarea", path: docs_textarea_path},
5252
{name: "Theme Toggle", path: docs_theme_toggle_path},
53+
{name: "Toast", path: docs_toast_path},
5354
{name: "Toggle", path: docs_toggle_path},
5455
{name: "Toggle Group", path: docs_toggle_group_path},
5556
{name: "Tooltip", path: docs_tooltip_path},
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
module Docs
4+
class ToastDemoController < ApplicationController
5+
def default = push(:default, "Event scheduled", "Friday at 3:00 PM")
6+
7+
def success = push(:success, "Saved successfully", "Your changes are live.")
8+
9+
def error = push(:error, "Something went wrong", "Please retry.")
10+
11+
def warning = push(:warning, "Heads up", "Storage almost full.")
12+
13+
def info = push(:info, "FYI", "New version available.")
14+
15+
def with_action
16+
render turbo_stream: build_stream(:default, "Email archived", nil, action_label: "Undo")
17+
end
18+
19+
private
20+
21+
def push(variant, title, description)
22+
render turbo_stream: build_stream(variant, title, description)
23+
end
24+
25+
def build_stream(variant, title, description, action_label: nil)
26+
content = ToastFragment.new(
27+
variant: variant,
28+
title: title,
29+
description: description,
30+
action_label: action_label
31+
).call
32+
turbo_stream.append("ruby-ui-toaster", content.html_safe)
33+
end
34+
35+
class ToastFragment < Phlex::HTML
36+
def initialize(variant:, title:, description:, action_label: nil)
37+
@variant = variant
38+
@title = title
39+
@description = description
40+
@action_label = action_label
41+
end
42+
43+
def view_template
44+
render RubyUI::ToastItem.new(variant: @variant) do
45+
render RubyUI::ToastIcon.new(variant: @variant)
46+
div(class: "flex flex-col gap-1 flex-1 min-w-0") do
47+
render RubyUI::ToastTitle.new { @title }
48+
render(RubyUI::ToastDescription.new { @description }) if @description
49+
end
50+
if @action_label
51+
render RubyUI::ToastAction.new(label: @action_label, on: "click->ruby-ui--toast#dismiss")
52+
end
53+
render RubyUI::ToastClose.new
54+
end
55+
end
56+
end
57+
end
58+
end

docs/app/controllers/docs_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ def theme_toggle
222222
render Views::Docs::ThemeToggle.new
223223
end
224224

225+
def toast
226+
render Views::Docs::Toast.new
227+
end
228+
225229
def toggle
226230
render Views::Docs::Toggle.new
227231
end

docs/app/javascript/controllers/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ import { application } from "./application"
77
import IframeThemeController from "./iframe_theme_controller"
88
application.register("iframe-theme", IframeThemeController)
99

10+
import ToastDemoController from "./toast_demo_controller"
11+
application.register("toast-demo", ToastDemoController)
12+
1013
import RubyUi__AccordionController from "./ruby_ui/accordion_controller"
1114
application.register("ruby-ui--accordion", RubyUi__AccordionController)
1215

1316
import RubyUi__AlertDialogController from "./ruby_ui/alert_dialog_controller"
1417
application.register("ruby-ui--alert-dialog", RubyUi__AlertDialogController)
1518

19+
import RubyUi__AvatarController from "./ruby_ui/avatar_controller"
20+
application.register("ruby-ui--avatar", RubyUi__AvatarController)
21+
1622
import RubyUi__CalendarController from "./ruby_ui/calendar_controller"
1723
application.register("ruby-ui--calendar", RubyUi__CalendarController)
1824

@@ -91,6 +97,12 @@ application.register("ruby-ui--tabs", RubyUi__TabsController)
9197
import RubyUi__ThemeToggleController from "./ruby_ui/theme_toggle_controller"
9298
application.register("ruby-ui--theme-toggle", RubyUi__ThemeToggleController)
9399

100+
import RubyUi__ToastController from "./ruby_ui/toast_controller"
101+
application.register("ruby-ui--toast", RubyUi__ToastController)
102+
103+
import RubyUi__ToasterController from "./ruby_ui/toaster_controller"
104+
application.register("ruby-ui--toaster", RubyUi__ToasterController)
105+
94106
import RubyUi__ToggleController from "./ruby_ui/toggle_controller"
95107
application.register("ruby-ui--toggle", RubyUi__ToggleController)
96108

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Controller } from "@hotwired/stimulus";
2+
3+
export default class extends Controller {
4+
static targets = ["image", "fallback"];
5+
6+
connect() {
7+
if (!this.hasImageTarget) return;
8+
9+
if (this.imageTarget.complete && this.imageTarget.naturalWidth > 0) {
10+
this.showImage();
11+
} else {
12+
this.showFallback();
13+
}
14+
}
15+
16+
showImage() {
17+
this.imageTargets.forEach((image) => image.classList.remove("hidden"));
18+
this.fallbackTargets.forEach((fallback) =>
19+
fallback.classList.add("hidden"),
20+
);
21+
}
22+
23+
showFallback() {
24+
this.imageTargets.forEach((image) => image.classList.add("hidden"));
25+
this.fallbackTargets.forEach((fallback) =>
26+
fallback.classList.remove("hidden"),
27+
);
28+
}
29+
}

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

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default class extends Controller {
66
"calendar",
77
"title",
88
"weekdaysTemplate",
9+
"disabledDateTemplate",
910
"selectedDateTemplate",
1011
"todayDateTemplate",
1112
"currentMonthDateTemplate",
@@ -16,6 +17,10 @@ export default class extends Controller {
1617
type: String,
1718
default: null,
1819
},
20+
minDate: {
21+
type: String,
22+
default: null,
23+
},
1924
viewDate: {
2025
type: String,
2126
default: new Date().toISOString().slice(0, 10),
@@ -43,13 +48,21 @@ export default class extends Controller {
4348

4449
selectDay(e) {
4550
e.preventDefault();
51+
if (this.isDateDisabled(e.currentTarget.dataset.day)) return;
52+
4653
// Set the selected date value
4754
this.selectedDateValue = e.currentTarget.dataset.day;
4855
}
4956

5057
selectedDateValueChanged(value, prevValue) {
58+
const selectedDate = this.selectedDate();
59+
if (!selectedDate) {
60+
this.updateCalendar();
61+
return;
62+
}
63+
5164
// update the viewDateValue to the first day of month of the selected date (This will trigger updateCalendar() function)
52-
const newViewDate = new Date(this.selectedDateValue);
65+
const newViewDate = new Date(selectedDate);
5366
newViewDate.setDate(2); // set the day to the 2nd (to avoid issues with months with different number of days and timezones)
5467
this.viewDateValue = newViewDate.toISOString().slice(0, 10);
5568

@@ -58,7 +71,7 @@ export default class extends Controller {
5871

5972
// update the input value
6073
this.rubyUiCalendarInputOutlets.forEach((outlet) => {
61-
const formattedDate = this.formatDate(this.selectedDate());
74+
const formattedDate = this.formatDate(selectedDate);
6275
outlet.setValue(formattedDate);
6376
});
6477
}
@@ -101,10 +114,20 @@ export default class extends Controller {
101114

102115
renderDay(day) {
103116
const today = new Date();
117+
const selectedDate = this.selectedDate();
104118
let dateHTML = "";
105119
const data = { day: day, dayDate: day.getDate() };
106120

107-
if (day.toDateString() === this.selectedDate().toDateString()) {
121+
if (this.isDateDisabled(day)) {
122+
// disabledDate
123+
dateHTML = Mustache.render(
124+
this.disabledDateTemplateTarget.innerHTML,
125+
data,
126+
);
127+
} else if (
128+
selectedDate &&
129+
day.toDateString() === selectedDate.toDateString()
130+
) {
108131
// selectedDate
109132
// Render the selected date template target innerHTML with Mustache
110133
dateHTML = Mustache.render(
@@ -137,13 +160,13 @@ export default class extends Controller {
137160
}
138161

139162
selectedDate() {
140-
return new Date(this.selectedDateValue);
163+
return this.parseDate(this.selectedDateValue);
141164
}
142165

143166
viewDate() {
144-
return this.viewDateValue
145-
? new Date(this.viewDateValue)
146-
: this.selectedDate();
167+
return (
168+
this.parseDate(this.viewDateValue) || this.selectedDate() || new Date()
169+
);
147170
}
148171

149172
getFullWeeksStartAndEndInMonth() {
@@ -246,4 +269,40 @@ export default class extends Controller {
246269
return "th";
247270
}
248271
}
272+
273+
minDate() {
274+
return this.parseDate(this.minDateValue);
275+
}
276+
277+
isDateDisabled(date) {
278+
const minDate = this.minDate();
279+
const candidate = this.parseDate(date);
280+
281+
if (!minDate || !candidate) return false;
282+
283+
return this.startOfDay(candidate) < this.startOfDay(minDate);
284+
}
285+
286+
parseDate(value) {
287+
if (!value) return null;
288+
if (value instanceof Date) return new Date(value);
289+
290+
const isoDate = value.toString().match(/^(\d{4})-(\d{2})-(\d{2})/);
291+
if (isoDate) {
292+
return new Date(
293+
Number(isoDate[1]),
294+
Number(isoDate[2]) - 1,
295+
Number(isoDate[3]),
296+
);
297+
}
298+
299+
const date = new Date(value);
300+
return Number.isNaN(date.getTime()) ? null : date;
301+
}
302+
303+
startOfDay(date) {
304+
const normalizedDate = new Date(date);
305+
normalizedDate.setHours(0, 0, 0, 0);
306+
return normalizedDate;
307+
}
249308
}

0 commit comments

Comments
 (0)