shadcn_phlexcomponents 0.1.14 → 0.1.17

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.js +107 -0
  3. data/app/javascript/controllers/alert_dialog_controller.js +7 -0
  4. data/app/javascript/controllers/avatar_controller.js +14 -0
  5. data/app/javascript/controllers/checkbox_controller.js +29 -0
  6. data/app/javascript/controllers/collapsible_controller.js +39 -0
  7. data/app/javascript/controllers/combobox_controller.js +278 -0
  8. data/app/javascript/controllers/command_controller.js +207 -0
  9. data/app/javascript/controllers/date_picker_controller.js +258 -0
  10. data/app/javascript/controllers/date_range_picker_controller.js +200 -0
  11. data/app/javascript/controllers/dialog_controller.js +83 -0
  12. data/app/javascript/controllers/dropdown_menu_controller.js +238 -0
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.js +118 -0
  14. data/app/javascript/controllers/form_field_controller.js +20 -0
  15. data/app/javascript/controllers/hover_card_controller.js +73 -0
  16. data/app/javascript/controllers/loading_button_controller.js +14 -0
  17. data/app/javascript/controllers/popover_controller.js +90 -0
  18. data/app/javascript/controllers/progress_controller.js +14 -0
  19. data/app/javascript/controllers/radio_group_controller.js +80 -0
  20. data/app/javascript/controllers/select_controller.js +265 -0
  21. data/app/javascript/controllers/sidebar_controller.js +29 -0
  22. data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
  23. data/app/javascript/controllers/slider_controller.js +82 -0
  24. data/app/javascript/controllers/switch_controller.js +26 -0
  25. data/app/javascript/controllers/tabs_controller.js +66 -0
  26. data/app/javascript/controllers/theme_switcher_controller.js +32 -0
  27. data/app/javascript/controllers/toast_container_controller.js +48 -0
  28. data/app/javascript/controllers/toast_controller.js +22 -0
  29. data/app/javascript/controllers/toggle_controller.js +20 -0
  30. data/app/javascript/controllers/toggle_group_controller.js +20 -0
  31. data/app/javascript/controllers/tooltip_controller.js +79 -0
  32. data/app/javascript/shadcn_phlexcomponents.js +60 -0
  33. data/app/javascript/utils/command.js +448 -0
  34. data/app/javascript/utils/floating_ui.js +160 -0
  35. data/app/javascript/utils/index.js +288 -0
  36. data/app/{javascript → typescript}/utils/index.ts +7 -0
  37. data/lib/install/install_shadcn_phlexcomponents.rb +10 -3
  38. data/lib/shadcn_phlexcomponents/components/combobox.rb +2 -6
  39. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  40. metadata +69 -35
  41. /data/app/{javascript → typescript}/controllers/accordion_controller.ts +0 -0
  42. /data/app/{javascript → typescript}/controllers/alert_dialog_controller.ts +0 -0
  43. /data/app/{javascript → typescript}/controllers/avatar_controller.ts +0 -0
  44. /data/app/{javascript → typescript}/controllers/checkbox_controller.ts +0 -0
  45. /data/app/{javascript → typescript}/controllers/collapsible_controller.ts +0 -0
  46. /data/app/{javascript → typescript}/controllers/combobox_controller.ts +0 -0
  47. /data/app/{javascript → typescript}/controllers/command_controller.ts +0 -0
  48. /data/app/{javascript → typescript}/controllers/date_picker_controller.ts +0 -0
  49. /data/app/{javascript → typescript}/controllers/date_range_picker_controller.ts +0 -0
  50. /data/app/{javascript → typescript}/controllers/dialog_controller.ts +0 -0
  51. /data/app/{javascript → typescript}/controllers/dropdown_menu_controller.ts +0 -0
  52. /data/app/{javascript → typescript}/controllers/dropdown_menu_sub_controller.ts +0 -0
  53. /data/app/{javascript → typescript}/controllers/form_field_controller.ts +0 -0
  54. /data/app/{javascript → typescript}/controllers/hover_card_controller.ts +0 -0
  55. /data/app/{javascript → typescript}/controllers/loading_button_controller.ts +0 -0
  56. /data/app/{javascript → typescript}/controllers/popover_controller.ts +0 -0
  57. /data/app/{javascript → typescript}/controllers/progress_controller.ts +0 -0
  58. /data/app/{javascript → typescript}/controllers/radio_group_controller.ts +0 -0
  59. /data/app/{javascript → typescript}/controllers/select_controller.ts +0 -0
  60. /data/app/{javascript → typescript}/controllers/sidebar_controller.ts +0 -0
  61. /data/app/{javascript → typescript}/controllers/sidebar_trigger_controller.ts +0 -0
  62. /data/app/{javascript → typescript}/controllers/slider_controller.ts +0 -0
  63. /data/app/{javascript → typescript}/controllers/switch_controller.ts +0 -0
  64. /data/app/{javascript → typescript}/controllers/tabs_controller.ts +0 -0
  65. /data/app/{javascript → typescript}/controllers/theme_switcher_controller.ts +0 -0
  66. /data/app/{javascript → typescript}/controllers/toast_container_controller.ts +0 -0
  67. /data/app/{javascript → typescript}/controllers/toast_controller.ts +0 -0
  68. /data/app/{javascript → typescript}/controllers/toggle_controller.ts +0 -0
  69. /data/app/{javascript → typescript}/controllers/toggle_group_controller.ts +0 -0
  70. /data/app/{javascript → typescript}/controllers/tooltip_controller.ts +0 -0
  71. /data/app/{javascript → typescript}/shadcn_phlexcomponents.ts +0 -0
  72. /data/app/{javascript → typescript}/utils/command.ts +0 -0
  73. /data/app/{javascript → typescript}/utils/floating_ui.ts +0 -0
@@ -0,0 +1,265 @@
1
+ import { useClickOutside } from "stimulus-use";
2
+ import { onKeydown, focusItemByIndex } from "./dropdown_menu_controller";
3
+ import { initFloatingUi } from "../utils/floating_ui";
4
+ import {
5
+ getSameLevelItems,
6
+ focusTrigger,
7
+ hideContent,
8
+ showContent,
9
+ lockScroll,
10
+ unlockScroll,
11
+ onClickOutside,
12
+ setGroupLabelsId,
13
+ getNextEnabledIndex,
14
+ getPreviousEnabledIndex,
15
+ focusElement,
16
+ } from "../utils";
17
+ import { Controller } from "@hotwired/stimulus";
18
+ const SelectController = class extends Controller {
19
+ // targets
20
+ static targets = [
21
+ "trigger",
22
+ "contentContainer",
23
+ "content",
24
+ "item",
25
+ "triggerText",
26
+ "group",
27
+ "select",
28
+ ];
29
+ // values
30
+ static values = {
31
+ isOpen: Boolean,
32
+ selected: String,
33
+ };
34
+ connect() {
35
+ this.items = getSameLevelItems({
36
+ content: this.contentTarget,
37
+ items: this.itemTargets,
38
+ closestContentSelector: '[data-select-target="content"]',
39
+ });
40
+ this.itemsInnerText = this.items.map((i) => i.innerText.trim());
41
+ this.searchString = "";
42
+ useClickOutside(this, {
43
+ element: this.contentTarget,
44
+ dispatchEvent: false,
45
+ });
46
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
47
+ setGroupLabelsId(this);
48
+ }
49
+ toggle(event) {
50
+ if (this.isOpenValue) {
51
+ this.close();
52
+ } else {
53
+ this.open(event);
54
+ }
55
+ }
56
+ open(event) {
57
+ this.isOpenValue = true;
58
+ let elementToFocus = null;
59
+ if (this.selectedValue) {
60
+ const item = this.itemTargets.find(
61
+ (i) => i.dataset.value === this.selectedValue,
62
+ );
63
+ if (item && !item.dataset.disabled) {
64
+ elementToFocus = item;
65
+ }
66
+ }
67
+ if (!elementToFocus) {
68
+ if (event instanceof KeyboardEvent) {
69
+ const key = event.key;
70
+ if (["ArrowDown", "Enter", " "].includes(key)) {
71
+ elementToFocus = this.items[0];
72
+ }
73
+ } else {
74
+ elementToFocus = this.contentTarget;
75
+ }
76
+ }
77
+ focusElement(elementToFocus);
78
+ }
79
+ close() {
80
+ this.isOpenValue = false;
81
+ }
82
+ onItemFocus(event) {
83
+ const item = event.currentTarget;
84
+ item.tabIndex = 0;
85
+ }
86
+ onItemBlur(event) {
87
+ const item = event.currentTarget;
88
+ item.tabIndex = -1;
89
+ }
90
+ focusItemByIndex(event = null, index = null) {
91
+ focusItemByIndex(this, event, index);
92
+ }
93
+ focusItem(event) {
94
+ const item = event.currentTarget;
95
+ const index = this.items.indexOf(item);
96
+ if (event instanceof KeyboardEvent) {
97
+ const key = event.key;
98
+ let newIndex = 0;
99
+ if (key === "ArrowUp") {
100
+ newIndex = getPreviousEnabledIndex({
101
+ items: this.items,
102
+ currentIndex: index,
103
+ wrapAround: false,
104
+ });
105
+ } else {
106
+ newIndex = getNextEnabledIndex({
107
+ items: this.items,
108
+ currentIndex: index,
109
+ wrapAround: false,
110
+ });
111
+ }
112
+ this.items[newIndex].focus();
113
+ } else {
114
+ // item mouseover event
115
+ this.items[index].focus();
116
+ }
117
+ }
118
+ focusContent() {
119
+ this.contentTarget.focus();
120
+ }
121
+ select(event) {
122
+ const item = event.currentTarget;
123
+ const value = item.dataset.value;
124
+ this.selectedValue = value;
125
+ this.close();
126
+ }
127
+ clickOutside(event) {
128
+ onClickOutside(this, event);
129
+ }
130
+ isOpenValueChanged(isOpen, previousIsOpen) {
131
+ if (isOpen) {
132
+ lockScroll(this.contentTarget.id);
133
+ showContent({
134
+ trigger: this.triggerTarget,
135
+ content: this.contentTarget,
136
+ contentContainer: this.contentContainerTarget,
137
+ setEqualWidth: true,
138
+ });
139
+ this.cleanup = initFloatingUi({
140
+ referenceElement: this.triggerTarget,
141
+ floatingElement: this.contentContainerTarget,
142
+ side: this.contentTarget.dataset.side,
143
+ align: this.contentTarget.dataset.align,
144
+ sideOffset: 4,
145
+ });
146
+ this.setupEventListeners();
147
+ } else {
148
+ unlockScroll(this.contentTarget.id);
149
+ hideContent({
150
+ trigger: this.triggerTarget,
151
+ content: this.contentTarget,
152
+ contentContainer: this.contentContainerTarget,
153
+ });
154
+ if (previousIsOpen) {
155
+ focusTrigger(this.triggerTarget);
156
+ }
157
+ this.cleanupEventListeners();
158
+ }
159
+ }
160
+ selectedValueChanged(value) {
161
+ const item = this.itemTargets.find((i) => i.dataset.value === value);
162
+ if (item) {
163
+ this.triggerTextTarget.textContent = item.textContent;
164
+ this.itemTargets.forEach((i) => {
165
+ if (i.dataset.value === value) {
166
+ i.setAttribute("aria-selected", "true");
167
+ } else {
168
+ i.setAttribute("aria-selected", "false");
169
+ }
170
+ });
171
+ this.selectTarget.value = value;
172
+ }
173
+ this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`;
174
+ const placeholder = this.triggerTarget.dataset.placeholder;
175
+ if (placeholder && this.triggerTarget.dataset.hasValue === "false") {
176
+ this.triggerTextTarget.textContent = placeholder;
177
+ }
178
+ }
179
+ disconnect() {
180
+ this.cleanupEventListeners();
181
+ }
182
+ onDOMKeydown(event) {
183
+ if (!this.isOpenValue) return;
184
+ onKeydown(this, event);
185
+ const { key, altKey, ctrlKey, metaKey } = event;
186
+ if (
187
+ key === "Backspace" ||
188
+ key === "Clear" ||
189
+ (key.length === 1 && key !== " " && !altKey && !ctrlKey && !metaKey)
190
+ ) {
191
+ this.handleSearch(key);
192
+ }
193
+ }
194
+ setupEventListeners() {
195
+ document.addEventListener("keydown", this.DOMKeydownListener);
196
+ }
197
+ cleanupEventListeners() {
198
+ if (this.cleanup) this.cleanup();
199
+ document.removeEventListener("keydown", this.DOMKeydownListener);
200
+ }
201
+ // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
202
+ handleSearch(char) {
203
+ const searchString = this.getSearchString(char);
204
+ const focusedItem = this.items.find(
205
+ (item) => document.activeElement === item,
206
+ );
207
+ const focusedIndex = focusedItem ? this.items.indexOf(focusedItem) : 0;
208
+ const searchIndex = this.getIndexByLetter(searchString, focusedIndex + 1);
209
+ // if a match was found, go to it
210
+ if (searchIndex >= 0) {
211
+ this.focusItemByIndex(null, searchIndex);
212
+ }
213
+ // if no matches, clear the timeout and search string
214
+ else {
215
+ window.clearTimeout(this.searchTimeout);
216
+ this.searchString = "";
217
+ }
218
+ }
219
+ filterItemsInnerText(items, filter) {
220
+ return items.filter((item) => {
221
+ const matches = item.toLowerCase().indexOf(filter.toLowerCase()) === 0;
222
+ return matches;
223
+ });
224
+ }
225
+ getSearchString(char) {
226
+ // reset typing timeout and start new timeout
227
+ // this allows us to make multiple-letter matches, like a native select
228
+ if (typeof this.searchTimeout === "number") {
229
+ window.clearTimeout(this.searchTimeout);
230
+ }
231
+ this.searchTimeout = window.setTimeout(() => {
232
+ this.searchString = "";
233
+ }, 500);
234
+ // add most recent letter to saved search string
235
+ this.searchString += char;
236
+ return this.searchString;
237
+ }
238
+ // return the index of an option from an array of options, based on a search string
239
+ // if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
240
+ getIndexByLetter(filter, startIndex) {
241
+ const orderedItems = [
242
+ ...this.itemsInnerText.slice(startIndex),
243
+ ...this.itemsInnerText.slice(0, startIndex),
244
+ ];
245
+ const firstMatch = this.filterItemsInnerText(orderedItems, filter)[0];
246
+ const allSameLetter = (array) =>
247
+ array.every((letter) => letter === array[0]);
248
+ // first check if there is an exact match for the typed string
249
+ if (firstMatch) {
250
+ const index = this.itemsInnerText.indexOf(firstMatch);
251
+ return index;
252
+ }
253
+ // if the same letter is being repeated, cycle through first-letter matches
254
+ else if (allSameLetter(filter.split(""))) {
255
+ const matches = this.filterItemsInnerText(orderedItems, filter[0]);
256
+ const index = this.itemsInnerText.indexOf(matches[0]);
257
+ return index;
258
+ }
259
+ // if no matches, return -1
260
+ else {
261
+ return -1;
262
+ }
263
+ }
264
+ };
265
+ export { SelectController };
@@ -0,0 +1,29 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ export default class default_1 extends Controller {
3
+ static targets = ["panel", "panelOffset"];
4
+ connect() {
5
+ this.width = this.element.offsetWidth;
6
+ }
7
+ toggle() {
8
+ if (this.isOpen()) {
9
+ this.close();
10
+ } else {
11
+ this.open();
12
+ }
13
+ }
14
+ open() {
15
+ this.element.dataset.state = "expanded";
16
+ this.element.dataset.collapsible = "";
17
+ this.panelTarget.style.removeProperty("left");
18
+ this.panelOffsetTarget.style.removeProperty("width");
19
+ }
20
+ close() {
21
+ this.element.dataset.state = "collapsed";
22
+ this.element.dataset.collapsible = "offcanvas";
23
+ this.panelTarget.style.left = `-${this.width}px`;
24
+ this.panelOffsetTarget.style.width = `${0}`;
25
+ }
26
+ isOpen() {
27
+ return this.element.dataset.state === "expanded";
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ export default class extends Controller {
3
+ connect() {
4
+ this.sidebarId = this.element.dataset.sidebarId;
5
+ }
6
+ toggle() {
7
+ const sidebar = this.application.getControllerForElementAndIdentifier(
8
+ document.querySelector(`#${this.sidebarId}`),
9
+ "sidebar",
10
+ );
11
+ if (sidebar) {
12
+ sidebar.toggle();
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,82 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import noUiSlider from "nouislider";
3
+ const SliderController = class extends Controller {
4
+ // targets
5
+ static targets = ["slider", "hiddenInput", "endHiddenInput"];
6
+ connect() {
7
+ this.range = this.element.dataset.range === "true";
8
+ this.DOMClickListener = this.onDOMClick.bind(this);
9
+ this.onUpdateValuesListener = this.onUpdateValues.bind(this);
10
+ const options = this.getOptions();
11
+ this.slider = noUiSlider.create(this.sliderTarget, options);
12
+ if (this.element.dataset.disabled === "true") {
13
+ this.slider.disable();
14
+ }
15
+ if (this.element.dataset.id) {
16
+ const lowerHandle =
17
+ this.slider.target.querySelector(".noUi-handle-lower");
18
+ if (lowerHandle) {
19
+ lowerHandle.id = this.element.dataset.id;
20
+ }
21
+ }
22
+ this.slider.on("update", this.onUpdateValuesListener);
23
+ document.addEventListener("click", this.DOMClickListener);
24
+ }
25
+ disconnect() {
26
+ document.removeEventListener("click", this.DOMClickListener);
27
+ }
28
+ getOptions() {
29
+ const defaultOptions = {
30
+ connect: this.range ? true : "lower",
31
+ tooltips: true,
32
+ };
33
+ defaultOptions.range = {
34
+ min: [parseFloat(this.element.dataset.min || "0")],
35
+ max: [parseFloat(this.element.dataset.max || "100")],
36
+ };
37
+ defaultOptions.step = parseFloat(this.element.dataset.step || "1");
38
+ if (this.range) {
39
+ defaultOptions.start = [
40
+ parseFloat(this.element.dataset.value || "0"),
41
+ parseFloat(this.element.dataset.endValue || "0"),
42
+ ];
43
+ defaultOptions.handleAttributes = [
44
+ { "aria-label": "lower" },
45
+ { "aria-label": "upper" },
46
+ ];
47
+ } else {
48
+ defaultOptions.start = [parseFloat(this.element.dataset.value || "0")];
49
+ defaultOptions.handleAttributes = [{ "aria-label": "lower" }];
50
+ }
51
+ defaultOptions.orientation =
52
+ this.element.dataset.orientation || "horizontal";
53
+ try {
54
+ return {
55
+ ...defaultOptions,
56
+ ...JSON.parse(this.element.dataset.options || ""),
57
+ };
58
+ } catch {
59
+ return defaultOptions;
60
+ }
61
+ }
62
+ onUpdateValues(values) {
63
+ this.hiddenInputTarget.value = `${values[0]}`;
64
+ if (this.range && this.hasEndHiddenInputTarget) {
65
+ this.endHiddenInputTarget.value = `${values[1]}`;
66
+ }
67
+ }
68
+ onDOMClick(event) {
69
+ const target = event.target;
70
+ // Focus handle of slider when label is clicked.
71
+ if (target instanceof HTMLLabelElement) {
72
+ const id = target.getAttribute("for");
73
+ if (id === this.element.dataset.id) {
74
+ const handle = document.querySelector(`#${id}`);
75
+ if (handle) {
76
+ handle.focus();
77
+ }
78
+ }
79
+ }
80
+ }
81
+ };
82
+ export { SliderController };
@@ -0,0 +1,26 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const SwitchController = class extends Controller {
3
+ // targets
4
+ static targets = ["input", "thumb"];
5
+ // values
6
+ static values = {
7
+ isChecked: Boolean,
8
+ };
9
+ toggle() {
10
+ this.isCheckedValue = !this.isCheckedValue;
11
+ }
12
+ isCheckedValueChanged(value) {
13
+ if (value) {
14
+ this.element.ariaChecked = "true";
15
+ this.element.dataset.state = "checked";
16
+ this.thumbTarget.dataset.state = "checked";
17
+ this.inputTarget.checked = true;
18
+ } else {
19
+ this.element.ariaChecked = "false";
20
+ this.element.dataset.state = "unchecked";
21
+ this.thumbTarget.dataset.state = "unchecked";
22
+ this.inputTarget.checked = false;
23
+ }
24
+ }
25
+ };
26
+ export { SwitchController };
@@ -0,0 +1,66 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { getNextEnabledIndex, getPreviousEnabledIndex } from "../utils";
3
+ const TabsController = class extends Controller {
4
+ // targets
5
+ static targets = ["trigger", "content"];
6
+ // values
7
+ static values = {
8
+ active: String,
9
+ };
10
+ connect() {
11
+ if (!this.activeValue) {
12
+ this.activeValue = this.triggerTargets[0].dataset.value;
13
+ }
14
+ }
15
+ setActiveTab(event) {
16
+ const target = event.currentTarget;
17
+ if (event instanceof MouseEvent) {
18
+ this.activeValue = target.dataset.value;
19
+ } else {
20
+ const key = event.key;
21
+ const focusableTriggers = this.triggerTargets.filter((t) => !t.disabled);
22
+ const index = focusableTriggers.indexOf(target);
23
+ let newIndex = 0;
24
+ if (key === "ArrowLeft") {
25
+ newIndex = getPreviousEnabledIndex({
26
+ items: focusableTriggers,
27
+ currentIndex: index,
28
+ wrapAround: true,
29
+ });
30
+ } else {
31
+ newIndex = getNextEnabledIndex({
32
+ items: focusableTriggers,
33
+ currentIndex: index,
34
+ wrapAround: true,
35
+ });
36
+ }
37
+ this.activeValue = focusableTriggers[newIndex].dataset.value;
38
+ focusableTriggers[newIndex].focus();
39
+ }
40
+ }
41
+ activeValueChanged(value) {
42
+ this.triggerTargets.forEach((trigger) => {
43
+ const triggerValue = trigger.dataset.value;
44
+ const content = this.contentTargets.find((c) => {
45
+ return c.dataset.value === triggerValue;
46
+ });
47
+ if (!content) {
48
+ throw new Error(
49
+ `Could not find TabsContent with value "${triggerValue}"`,
50
+ );
51
+ }
52
+ if (triggerValue === value) {
53
+ trigger.ariaSelected = "true";
54
+ trigger.tabIndex = 0;
55
+ trigger.dataset.state = "active";
56
+ content.classList.remove("hidden");
57
+ } else {
58
+ trigger.ariaSelected = "false";
59
+ trigger.tabIndex = -1;
60
+ trigger.dataset.state = "inactive";
61
+ content.classList.add("hidden");
62
+ }
63
+ });
64
+ }
65
+ };
66
+ export { TabsController };
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const ThemeSwitcherController = class extends Controller {
3
+ initialize() {
4
+ if (
5
+ localStorage.theme === "dark" ||
6
+ (!("theme" in localStorage) &&
7
+ window.matchMedia("(prefers-color-scheme: dark)").matches)
8
+ ) {
9
+ this.setDarkMode();
10
+ } else {
11
+ this.setLightMode();
12
+ }
13
+ }
14
+ toggle() {
15
+ if (document.documentElement.classList.contains("dark")) {
16
+ this.setLightMode();
17
+ } else {
18
+ this.setDarkMode();
19
+ }
20
+ }
21
+ setLightMode() {
22
+ localStorage.theme = "light";
23
+ document.documentElement.classList.remove("dark");
24
+ document.documentElement.style.colorScheme = "";
25
+ }
26
+ setDarkMode() {
27
+ localStorage.theme = "dark";
28
+ document.documentElement.classList.add("dark");
29
+ document.documentElement.style.colorScheme = "dark";
30
+ }
31
+ };
32
+ export { ThemeSwitcherController };
@@ -0,0 +1,48 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import DOMPurify from "dompurify";
3
+ const ToastContainerController = class extends Controller {
4
+ addToast({
5
+ title,
6
+ description,
7
+ action,
8
+ variant = "default",
9
+ duration = 5000,
10
+ }) {
11
+ const template =
12
+ variant === "default"
13
+ ? this.element.querySelector('[data-variant="default"]')
14
+ : this.element.querySelector('[data-variant="destructive"]');
15
+ const clone = template.content.cloneNode(true);
16
+ const toastTemplate = clone.querySelector(
17
+ '[data-shadcn-phlexcomponents="toast"]',
18
+ );
19
+ toastTemplate.dataset.duration = String(duration);
20
+ const titleTemplate = clone.querySelector(
21
+ '[data-shadcn-phlexcomponents="toast-title"]',
22
+ );
23
+ const descriptionTemplate = clone.querySelector(
24
+ '[data-shadcn-phlexcomponents="toast-description"]',
25
+ );
26
+ const actionTemplate = clone.querySelector(
27
+ '[data-shadcn-phlexcomponents="toast-action"]',
28
+ );
29
+ titleTemplate.innerHTML = DOMPurify.sanitize(title);
30
+ if (description) {
31
+ descriptionTemplate.innerHTML = DOMPurify.sanitize(description);
32
+ } else {
33
+ descriptionTemplate.remove();
34
+ }
35
+ if (action) {
36
+ const element = document.createElement("div");
37
+ element.innerHTML = DOMPurify.sanitize(action);
38
+ const actionElement = element.firstElementChild;
39
+ const classes = actionTemplate.classList;
40
+ actionElement.classList.add(...classes);
41
+ actionTemplate.replaceWith(actionElement);
42
+ } else {
43
+ actionTemplate.remove();
44
+ }
45
+ this.element.append(clone);
46
+ }
47
+ };
48
+ export { ToastContainerController };
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { ANIMATION_OUT_DELAY } from "../utils";
3
+ const ToastController = class extends Controller {
4
+ connect() {
5
+ this.duration = Number(this.element.dataset.duration);
6
+ this.close();
7
+ }
8
+ cancelClose() {
9
+ window.clearTimeout(this.closeTimeout);
10
+ }
11
+ close() {
12
+ if (this.duration > 0) {
13
+ this.closeTimeout = window.setTimeout(() => {
14
+ this.element.dataset.state = "closed";
15
+ setTimeout(() => {
16
+ this.element.remove();
17
+ }, ANIMATION_OUT_DELAY);
18
+ }, this.duration);
19
+ }
20
+ }
21
+ };
22
+ export { ToastController };
@@ -0,0 +1,20 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const ToggleController = class extends Controller {
3
+ // values
4
+ static values = {
5
+ isOn: Boolean,
6
+ };
7
+ toggle() {
8
+ this.isOnValue = !this.isOnValue;
9
+ }
10
+ isOnValueChanged(isOn) {
11
+ if (isOn) {
12
+ this.element.dataset.state = "on";
13
+ this.element.ariaPressed = "true";
14
+ } else {
15
+ this.element.dataset.state = "off";
16
+ this.element.ariaPressed = "false";
17
+ }
18
+ }
19
+ };
20
+ export { ToggleController };
@@ -0,0 +1,20 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ const ToggleController = class extends Controller {
3
+ // values
4
+ static values = {
5
+ isOn: Boolean,
6
+ };
7
+ toggle() {
8
+ this.isOnValue = !this.isOnValue;
9
+ }
10
+ isOnValueChanged(isOn) {
11
+ if (isOn) {
12
+ this.element.dataset.state = "on";
13
+ this.element.ariaPressed = "true";
14
+ } else {
15
+ this.element.dataset.state = "off";
16
+ this.element.ariaPressed = "false";
17
+ }
18
+ }
19
+ };
20
+ export { ToggleController };