shadcn_phlexcomponents 0.1.16 → 0.1.18

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.js +90 -100
  3. data/app/javascript/controllers/alert_dialog_controller.js +4 -4
  4. data/app/javascript/controllers/avatar_controller.js +11 -11
  5. data/app/javascript/controllers/checkbox_controller.js +26 -25
  6. data/app/javascript/controllers/collapsible_controller.js +36 -34
  7. data/app/javascript/controllers/combobox_controller.js +231 -262
  8. data/app/javascript/controllers/command_controller.js +184 -204
  9. data/app/javascript/controllers/date_picker_controller.js +257 -240
  10. data/app/javascript/controllers/date_range_picker_controller.js +200 -188
  11. data/app/javascript/controllers/dialog_controller.js +78 -78
  12. data/app/javascript/controllers/dropdown_menu_controller.js +208 -228
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.js +97 -110
  14. data/app/javascript/controllers/form_field_controller.js +16 -16
  15. data/app/javascript/controllers/hover_card_controller.js +71 -68
  16. data/app/javascript/controllers/loading_button_controller.js +10 -10
  17. data/app/javascript/controllers/popover_controller.js +78 -84
  18. data/app/javascript/controllers/progress_controller.js +11 -11
  19. data/app/javascript/controllers/radio_group_controller.js +74 -74
  20. data/app/javascript/controllers/select_controller.js +232 -246
  21. data/app/javascript/controllers/sidebar_controller.js +27 -26
  22. data/app/javascript/controllers/sidebar_trigger_controller.js +9 -12
  23. data/app/javascript/controllers/slider_controller.js +74 -73
  24. data/app/javascript/controllers/switch_controller.js +23 -22
  25. data/app/javascript/controllers/tabs_controller.js +61 -60
  26. data/app/javascript/controllers/theme_switcher_controller.js +27 -27
  27. data/app/javascript/controllers/toast_container_controller.js +31 -44
  28. data/app/javascript/controllers/toast_controller.js +18 -18
  29. data/app/javascript/controllers/toggle_controller.js +17 -16
  30. data/app/javascript/controllers/toggle_group_controller.js +17 -16
  31. data/app/javascript/controllers/tooltip_controller.js +77 -74
  32. data/app/javascript/shadcn_phlexcomponents.js +58 -58
  33. data/app/javascript/utils/command.js +334 -392
  34. data/app/javascript/utils/floating_ui.js +108 -147
  35. data/app/javascript/utils/index.js +190 -253
  36. data/app/stylesheets/date_picker.css +1 -1
  37. data/app/stylesheets/shadcn_phlexcomponents.css +173 -0
  38. data/app/typescript/controllers/combobox_controller.ts +0 -1
  39. data/app/typescript/controllers/date_picker_controller.ts +25 -7
  40. data/app/typescript/controllers/tooltip_controller.ts +1 -1
  41. data/app/typescript/utils/command.ts +0 -2
  42. data/app/typescript/utils/floating_ui.ts +11 -20
  43. data/app/typescript/utils/index.ts +0 -7
  44. data/lib/shadcn_phlexcomponents/components/accordion.rb +1 -1
  45. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +6 -6
  46. data/lib/shadcn_phlexcomponents/components/base.rb +2 -2
  47. data/lib/shadcn_phlexcomponents/components/combobox.rb +15 -19
  48. data/lib/shadcn_phlexcomponents/components/command.rb +13 -13
  49. data/lib/shadcn_phlexcomponents/components/date_picker.rb +18 -12
  50. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +7 -3
  51. data/lib/shadcn_phlexcomponents/components/dialog.rb +6 -6
  52. data/lib/shadcn_phlexcomponents/components/sheet.rb +7 -7
  53. data/lib/shadcn_phlexcomponents/components/toggle.rb +1 -1
  54. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  55. metadata +2 -1
@@ -1,258 +1,275 @@
1
- import { Controller } from "@hotwired/stimulus";
2
- import { useClickOutside } from "stimulus-use";
3
- import {
4
- focusTrigger,
5
- getFocusableElements,
6
- showContent,
7
- hideContent,
8
- lockScroll,
9
- unlockScroll,
10
- handleTabNavigation,
11
- focusElement,
12
- } from "../utils";
13
- import { initFloatingUi } from "../utils/floating_ui";
14
- import { Calendar } from "vanilla-calendar-pro";
15
- import Inputmask from "inputmask";
16
- import dayjs from "dayjs";
17
- import customParseFormat from "dayjs/plugin/customParseFormat";
18
- import utc from "dayjs/plugin/utc";
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import { useClickOutside } from 'stimulus-use';
3
+ import { focusTrigger, getFocusableElements, showContent, hideContent, lockScroll, unlockScroll, handleTabNavigation, focusElement, } from '../utils';
4
+ import { initFloatingUi } from '../utils/floating_ui';
5
+ import { Calendar } from 'vanilla-calendar-pro';
6
+ import Inputmask from 'inputmask';
7
+ import dayjs from 'dayjs';
8
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
9
+ import utc from 'dayjs/plugin/utc';
19
10
  dayjs.extend(customParseFormat);
20
11
  dayjs.extend(utc);
21
- const DAYJS_FORMAT = "YYYY-MM-DD";
12
+ const SMALL_SCREEN_BREAKPOINT = 768;
13
+ const isSmallScreen = () => {
14
+ return window.innerWidth < SMALL_SCREEN_BREAKPOINT;
15
+ };
16
+ const DAYJS_FORMAT = 'YYYY-MM-DD';
22
17
  const DatePickerController = class extends Controller {
23
- // targets
24
- static targets = [
25
- "trigger",
26
- "triggerText",
27
- "contentContainer",
28
- "content",
29
- "input",
30
- "hiddenInput",
31
- "inputContainer",
32
- "calendar",
33
- "overlay",
34
- ];
35
- // values
36
- static values = { isOpen: Boolean, date: String };
37
- connect() {
38
- this.format = this.element.dataset.format || "DD/MM/YYYY";
39
- this.mask = this.element.dataset.mask === "true";
40
- this.DOMKeydownListener = this.onDOMKeydown.bind(this);
41
- this.onClickDateListener = this.onClickDate.bind(this);
42
- const options = this.getOptions();
43
- this.calendar = new Calendar(this.calendarTarget, options);
44
- this.calendar.init();
45
- if (this.hasInputTarget && this.mask) {
46
- this.setupInputMask();
18
+ // targets
19
+ static targets = [
20
+ 'trigger',
21
+ 'triggerText',
22
+ 'contentContainer',
23
+ 'content',
24
+ 'input',
25
+ 'hiddenInput',
26
+ 'inputContainer',
27
+ 'calendar',
28
+ 'overlay',
29
+ ];
30
+ // values
31
+ static values = { isOpen: Boolean, date: String };
32
+ connect() {
33
+ this.format = this.element.dataset.format || 'DD/MM/YYYY';
34
+ this.mask = this.element.dataset.mask === 'true';
35
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this);
36
+ this.onClickDateListener = this.onClickDate.bind(this);
37
+ const options = this.getOptions();
38
+ this.calendar = new Calendar(this.calendarTarget, options);
39
+ this.calendar.init();
40
+ if (this.hasInputTarget && this.mask) {
41
+ this.setupInputMask();
42
+ }
43
+ this.calendarTarget.removeAttribute('tabindex');
44
+ useClickOutside(this, { element: this.contentTarget, dispatchEvent: false });
47
45
  }
48
- this.calendarTarget.removeAttribute("tabindex");
49
- useClickOutside(this, {
50
- element: this.contentTarget,
51
- dispatchEvent: false,
52
- });
53
- }
54
- contentContainerTargetConnected() {
55
- // Datepicker is shown as a dialog on small screens
56
- const styles = window.getComputedStyle(this.contentContainerTarget);
57
- this.isMobile = styles.translate === "-50%";
58
- }
59
- toggle() {
60
- if (this.isOpenValue) {
61
- this.close();
62
- } else {
63
- this.open();
46
+ contentContainerTargetConnected() {
47
+ if (isSmallScreen()) {
48
+ const windowHeight = window.innerHeight;
49
+ const datePickerHeight = this.identifier === 'date-picker' ? 300 : 600;
50
+ let topOffset = 0;
51
+ if (windowHeight > datePickerHeight) {
52
+ topOffset = (windowHeight - datePickerHeight) / 2;
53
+ }
54
+ Object.assign(this.contentContainerTarget.style, {
55
+ top: `${topOffset}px`,
56
+ left: '50%',
57
+ transform: 'translateX(-50%)',
58
+ pointerEvents: 'auto',
59
+ });
60
+ }
64
61
  }
65
- }
66
- open() {
67
- this.isOpenValue = true;
68
- }
69
- close() {
70
- this.isOpenValue = false;
71
- }
72
- inputBlur() {
73
- let dateDisplay = "";
74
- const date = this.calendar.context.selectedDates[0];
75
- if (date) {
76
- dateDisplay = dayjs(date).format(this.format);
62
+ toggle() {
63
+ if (this.isOpenValue) {
64
+ this.close();
65
+ }
66
+ else {
67
+ this.open();
68
+ }
77
69
  }
78
- this.inputTarget.value = dateDisplay;
79
- this.inputContainerTarget.dataset.focus = "false";
80
- }
81
- inputDate(event) {
82
- const value = event.target.value;
83
- if (value.length === 0) {
84
- this.calendar.set({
85
- selectedDates: [],
86
- });
87
- this.dateValue = "";
70
+ open() {
71
+ this.isOpenValue = true;
88
72
  }
89
- if (value.length > 0 && dayjs(value, this.format, true).isValid()) {
90
- const dayjsDate = dayjs(value, this.format).format(DAYJS_FORMAT);
91
- this.calendar.set({
92
- selectedDates: [dayjsDate],
93
- });
94
- this.dateValue = dayjsDate;
73
+ close() {
74
+ this.isOpenValue = false;
95
75
  }
96
- }
97
- setContainerFocus() {
98
- this.inputContainerTarget.dataset.focus = "true";
99
- }
100
- clickOutside(event) {
101
- const target = event.target;
102
- // Let trigger handle state
103
- if (target === this.triggerTarget) return;
104
- if (this.triggerTarget.contains(target)) return;
105
- if (this.triggerTarget.id === target.getAttribute("for")) return;
106
- this.close();
107
- }
108
- isOpenValueChanged(isOpen, previousIsOpen) {
109
- if (isOpen) {
110
- showContent({
111
- trigger: this.triggerTarget,
112
- content: this.contentTarget,
113
- contentContainer: this.contentContainerTarget,
114
- });
115
- // Prevent width from changing when changing to month/year view
116
- if (!this.contentTarget.dataset.width) {
117
- const contentWidth = this.contentTarget.offsetWidth;
118
- this.contentTarget.dataset.width = `${contentWidth}`;
119
- this.contentTarget.style.maxWidth = `${contentWidth}px`;
120
- this.contentTarget.style.minWidth = `${contentWidth}px`;
121
- }
122
- if (this.isMobile) {
123
- lockScroll(this.contentTarget.id);
124
- this.overlayTarget.style.display = "";
125
- this.overlayTarget.dataset.state = "open";
126
- } else {
127
- this.cleanup = initFloatingUi({
128
- referenceElement: this.hasInputTarget
129
- ? this.inputTarget
130
- : this.triggerTarget,
131
- floatingElement: this.contentContainerTarget,
132
- side: this.contentTarget.dataset.side,
133
- align: this.contentTarget.dataset.align,
134
- sideOffset: 4,
135
- });
136
- }
137
- let elementToFocus = null;
138
- const focusableElements = getFocusableElements(this.contentTarget);
139
- const selectedElement = Array.from(focusableElements).find(
140
- (e) => e.ariaSelected,
141
- );
142
- const currentElement = this.contentTarget.querySelector("[aria-current]");
143
- if (selectedElement) {
144
- elementToFocus = selectedElement;
145
- } else if (currentElement) {
146
- const firstElementChild = currentElement.firstElementChild;
147
- elementToFocus = firstElementChild;
148
- } else {
149
- elementToFocus = focusableElements[0];
150
- }
151
- focusElement(elementToFocus);
152
- this.setupEventListeners();
153
- } else {
154
- hideContent({
155
- trigger: this.triggerTarget,
156
- content: this.contentTarget,
157
- contentContainer: this.contentContainerTarget,
158
- });
159
- if (this.isMobile) {
160
- unlockScroll(this.contentTarget.id);
161
- this.overlayTarget.style.display = "none";
162
- this.overlayTarget.dataset.state = "closed";
163
- }
164
- if (previousIsOpen) {
165
- focusTrigger(this.triggerTarget);
166
- }
167
- this.cleanupEventListeners();
76
+ inputBlur() {
77
+ let dateDisplay = '';
78
+ const date = this.calendar.context.selectedDates[0];
79
+ if (date) {
80
+ dateDisplay = dayjs(date).format(this.format);
81
+ }
82
+ this.inputTarget.value = dateDisplay;
83
+ this.inputContainerTarget.dataset.focus = 'false';
84
+ }
85
+ inputDate(event) {
86
+ const value = event.target.value;
87
+ if (value.length === 0) {
88
+ this.calendar.set({
89
+ selectedDates: [],
90
+ });
91
+ this.dateValue = '';
92
+ }
93
+ if (value.length > 0 && dayjs(value, this.format, true).isValid()) {
94
+ const dayjsDate = dayjs(value, this.format).format(DAYJS_FORMAT);
95
+ this.calendar.set({
96
+ selectedDates: [dayjsDate],
97
+ });
98
+ this.dateValue = dayjsDate;
99
+ }
100
+ }
101
+ setContainerFocus() {
102
+ this.inputContainerTarget.dataset.focus = 'true';
103
+ }
104
+ clickOutside(event) {
105
+ const target = event.target;
106
+ // Let trigger handle state
107
+ if (target === this.triggerTarget)
108
+ return;
109
+ if (this.triggerTarget.contains(target))
110
+ return;
111
+ if (this.triggerTarget.id === target.getAttribute('for'))
112
+ return;
113
+ this.close();
114
+ }
115
+ isOpenValueChanged(isOpen, previousIsOpen) {
116
+ if (isOpen) {
117
+ showContent({
118
+ trigger: this.triggerTarget,
119
+ content: this.contentTarget,
120
+ contentContainer: this.contentContainerTarget,
121
+ });
122
+ // Prevent width from changing when changing to month/year view
123
+ if (!this.contentTarget.dataset.width) {
124
+ const contentWidth = this.contentTarget.offsetWidth;
125
+ this.contentTarget.dataset.width = `${contentWidth}`;
126
+ this.contentTarget.style.maxWidth = `${contentWidth}px`;
127
+ this.contentTarget.style.minWidth = `${contentWidth}px`;
128
+ }
129
+ if (isSmallScreen()) {
130
+ lockScroll(this.contentTarget.id);
131
+ this.overlayTarget.style.display = '';
132
+ this.overlayTarget.dataset.state = 'open';
133
+ }
134
+ else {
135
+ this.cleanup = initFloatingUi({
136
+ referenceElement: this.hasInputTarget
137
+ ? this.inputTarget
138
+ : this.triggerTarget,
139
+ floatingElement: this.contentContainerTarget,
140
+ side: this.contentTarget.dataset.side,
141
+ align: this.contentTarget.dataset.align,
142
+ sideOffset: 4,
143
+ });
144
+ }
145
+ let elementToFocus = null;
146
+ const focusableElements = getFocusableElements(this.contentTarget);
147
+ const selectedElement = Array.from(focusableElements).find((e) => e.ariaSelected);
148
+ const currentElement = this.contentTarget.querySelector('[aria-current]');
149
+ if (selectedElement) {
150
+ elementToFocus = selectedElement;
151
+ }
152
+ else if (currentElement) {
153
+ const firstElementChild = currentElement.firstElementChild;
154
+ elementToFocus = firstElementChild;
155
+ }
156
+ else {
157
+ elementToFocus = focusableElements[0];
158
+ }
159
+ focusElement(elementToFocus);
160
+ this.setupEventListeners();
161
+ }
162
+ else {
163
+ hideContent({
164
+ trigger: this.triggerTarget,
165
+ content: this.contentTarget,
166
+ contentContainer: this.contentContainerTarget,
167
+ });
168
+ if (isSmallScreen()) {
169
+ unlockScroll(this.contentTarget.id);
170
+ this.overlayTarget.style.display = 'none';
171
+ this.overlayTarget.dataset.state = 'closed';
172
+ }
173
+ if (previousIsOpen) {
174
+ focusTrigger(this.triggerTarget);
175
+ }
176
+ this.cleanupEventListeners();
177
+ }
168
178
  }
169
- }
170
- dateValueChanged(value) {
171
- if (value && value.length > 0) {
172
- const dayjsDate = dayjs(value);
173
- const formattedDate = dayjsDate.format(this.format);
174
- if (this.hasInputTarget) this.inputTarget.value = formattedDate;
175
- if (this.hasTriggerTextTarget) {
176
- this.triggerTarget.dataset.hasValue = "true";
177
- this.triggerTextTarget.textContent = formattedDate;
178
- }
179
- this.hiddenInputTarget.value = dayjsDate.utc().format();
180
- } else {
181
- if (this.hasInputTarget) this.inputTarget.value = "";
182
- if (this.hasTriggerTextTarget) {
183
- this.triggerTarget.dataset.hasValue = "false";
184
- if (this.triggerTarget.dataset.placeholder) {
185
- this.triggerTextTarget.textContent =
186
- this.triggerTarget.dataset.placeholder;
187
- } else {
188
- this.triggerTextTarget.textContent = "";
179
+ dateValueChanged(value) {
180
+ if (value && value.length > 0) {
181
+ const dayjsDate = dayjs(value);
182
+ const formattedDate = dayjsDate.format(this.format);
183
+ if (this.hasInputTarget)
184
+ this.inputTarget.value = formattedDate;
185
+ if (this.hasTriggerTextTarget) {
186
+ this.triggerTarget.dataset.hasValue = 'true';
187
+ this.triggerTextTarget.textContent = formattedDate;
188
+ }
189
+ this.hiddenInputTarget.value = dayjsDate.utc().format();
190
+ }
191
+ else {
192
+ if (this.hasInputTarget)
193
+ this.inputTarget.value = '';
194
+ if (this.hasTriggerTextTarget) {
195
+ this.triggerTarget.dataset.hasValue = 'false';
196
+ if (this.triggerTarget.dataset.placeholder) {
197
+ this.triggerTextTarget.textContent =
198
+ this.triggerTarget.dataset.placeholder;
199
+ }
200
+ else {
201
+ this.triggerTextTarget.textContent = '';
202
+ }
203
+ }
204
+ this.hiddenInputTarget.value = '';
189
205
  }
190
- }
191
- this.hiddenInputTarget.value = "";
192
206
  }
193
- }
194
- disconnect() {
195
- this.cleanupEventListeners();
196
- }
197
- onClickDate(self) {
198
- const date = self.context.selectedDates[0];
199
- if (date) {
200
- this.dateValue = date;
201
- this.close();
202
- } else {
203
- this.dateValue = "";
207
+ disconnect() {
208
+ this.cleanupEventListeners();
204
209
  }
205
- }
206
- setupEventListeners() {
207
- document.addEventListener("keydown", this.DOMKeydownListener);
208
- }
209
- cleanupEventListeners() {
210
- if (this.cleanup) this.cleanup();
211
- document.removeEventListener("keydown", this.DOMKeydownListener);
212
- }
213
- getOptions() {
214
- let options = {
215
- type: "default",
216
- enableJumpToSelectedDate: true,
217
- onClickDate: this.onClickDateListener,
218
- };
219
- const date = this.element.dataset.value;
220
- if (date && dayjs(date).isValid()) {
221
- const dayjsDate = dayjs(date).format(DAYJS_FORMAT);
222
- options.selectedDates = [dayjsDate];
210
+ onClickDate(self) {
211
+ const date = self.context.selectedDates[0];
212
+ if (date) {
213
+ this.dateValue = date;
214
+ this.close();
215
+ }
216
+ else {
217
+ this.dateValue = '';
218
+ }
223
219
  }
224
- try {
225
- options = {
226
- ...options,
227
- ...JSON.parse(this.element.dataset.options || ""),
228
- };
229
- } catch {
230
- // noop
220
+ setupEventListeners() {
221
+ document.addEventListener('keydown', this.DOMKeydownListener);
222
+ }
223
+ cleanupEventListeners() {
224
+ if (this.cleanup)
225
+ this.cleanup();
226
+ document.removeEventListener('keydown', this.DOMKeydownListener);
227
+ }
228
+ getOptions() {
229
+ let options = {
230
+ type: 'default',
231
+ enableJumpToSelectedDate: true,
232
+ onClickDate: this.onClickDateListener,
233
+ };
234
+ const date = this.element.dataset.value;
235
+ if (date && dayjs(date).isValid()) {
236
+ const dayjsDate = dayjs(date).format(DAYJS_FORMAT);
237
+ options.selectedDates = [dayjsDate];
238
+ }
239
+ try {
240
+ options = {
241
+ ...options,
242
+ ...JSON.parse(this.element.dataset.options || ''),
243
+ };
244
+ }
245
+ catch {
246
+ // noop
247
+ }
248
+ if (options.selectedDates && options.selectedDates.length > 0) {
249
+ this.dateValue = `${options.selectedDates[0]}`;
250
+ }
251
+ return options;
231
252
  }
232
- if (options.selectedDates && options.selectedDates.length > 0) {
233
- this.dateValue = `${options.selectedDates[0]}`;
253
+ onDOMKeydown(event) {
254
+ if (!this.isOpenValue)
255
+ return;
256
+ const key = event.key;
257
+ if (key === 'Escape') {
258
+ this.close();
259
+ }
260
+ else if (key === 'Tab') {
261
+ handleTabNavigation(this.contentTarget, event);
262
+ }
263
+ else if (['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft'].includes(key) &&
264
+ document.activeElement != this.inputTarget) {
265
+ event.preventDefault();
266
+ }
234
267
  }
235
- return options;
236
- }
237
- onDOMKeydown(event) {
238
- if (!this.isOpenValue) return;
239
- const key = event.key;
240
- if (key === "Escape") {
241
- this.close();
242
- } else if (key === "Tab") {
243
- handleTabNavigation(this.contentTarget, event);
244
- } else if (
245
- ["ArrowUp", "ArrowDown", "ArrowRight", "ArrowLeft"].includes(key) &&
246
- document.activeElement != this.inputTarget
247
- ) {
248
- event.preventDefault();
268
+ setupInputMask() {
269
+ const im = new Inputmask(this.format.replace(/[^/]/g, '9'), {
270
+ showMaskOnHover: false,
271
+ });
272
+ im.mask(this.inputTarget);
249
273
  }
250
- }
251
- setupInputMask() {
252
- const im = new Inputmask(this.format.replace(/[^/]/g, "9"), {
253
- showMaskOnHover: false,
254
- });
255
- im.mask(this.inputTarget);
256
- }
257
274
  };
258
275
  export { DatePickerController };