shadcn_phlexcomponents 0.1.11 → 0.1.14

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.ts +65 -62
  3. data/app/javascript/controllers/alert_dialog_controller.ts +12 -0
  4. data/app/javascript/controllers/avatar_controller.ts +7 -2
  5. data/app/javascript/controllers/checkbox_controller.ts +11 -4
  6. data/app/javascript/controllers/collapsible_controller.ts +12 -5
  7. data/app/javascript/controllers/combobox_controller.ts +270 -39
  8. data/app/javascript/controllers/command_controller.ts +223 -51
  9. data/app/javascript/controllers/date_picker_controller.ts +185 -125
  10. data/app/javascript/controllers/date_range_picker_controller.ts +89 -79
  11. data/app/javascript/controllers/dialog_controller.ts +59 -57
  12. data/app/javascript/controllers/dropdown_menu_controller.ts +212 -36
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.ts +31 -29
  14. data/app/javascript/controllers/form_field_controller.ts +6 -1
  15. data/app/javascript/controllers/hover_card_controller.ts +36 -26
  16. data/app/javascript/controllers/loading_button_controller.ts +6 -1
  17. data/app/javascript/controllers/popover_controller.ts +42 -65
  18. data/app/javascript/controllers/progress_controller.ts +9 -3
  19. data/app/javascript/controllers/radio_group_controller.ts +16 -9
  20. data/app/javascript/controllers/select_controller.ts +206 -65
  21. data/app/javascript/controllers/slider_controller.ts +23 -16
  22. data/app/javascript/controllers/switch_controller.ts +11 -4
  23. data/app/javascript/controllers/tabs_controller.ts +26 -18
  24. data/app/javascript/controllers/theme_switcher_controller.ts +6 -1
  25. data/app/javascript/controllers/toast_container_controller.ts +6 -1
  26. data/app/javascript/controllers/toast_controller.ts +7 -1
  27. data/app/javascript/controllers/toggle_controller.ts +28 -0
  28. data/app/javascript/controllers/toggle_group_controller.ts +28 -0
  29. data/app/javascript/controllers/tooltip_controller.ts +43 -31
  30. data/app/javascript/shadcn_phlexcomponents.ts +29 -25
  31. data/app/javascript/utils/command.ts +544 -0
  32. data/app/javascript/utils/floating_ui.ts +196 -0
  33. data/app/javascript/utils/index.ts +417 -0
  34. data/app/stylesheets/date_picker.css +118 -0
  35. data/lib/shadcn_phlexcomponents/alias.rb +3 -0
  36. data/lib/shadcn_phlexcomponents/components/accordion.rb +2 -1
  37. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +18 -15
  38. data/lib/shadcn_phlexcomponents/components/base.rb +14 -0
  39. data/lib/shadcn_phlexcomponents/components/collapsible.rb +1 -2
  40. data/lib/shadcn_phlexcomponents/components/combobox.rb +87 -57
  41. data/lib/shadcn_phlexcomponents/components/command.rb +77 -47
  42. data/lib/shadcn_phlexcomponents/components/date_picker.rb +25 -81
  43. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +21 -4
  44. data/lib/shadcn_phlexcomponents/components/dialog.rb +14 -12
  45. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +5 -4
  46. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +2 -1
  47. data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
  48. data/lib/shadcn_phlexcomponents/components/form.rb +14 -0
  49. data/lib/shadcn_phlexcomponents/components/hover_card.rb +3 -2
  50. data/lib/shadcn_phlexcomponents/components/popover.rb +3 -3
  51. data/lib/shadcn_phlexcomponents/components/select.rb +10 -25
  52. data/lib/shadcn_phlexcomponents/components/sheet.rb +15 -11
  53. data/lib/shadcn_phlexcomponents/components/table.rb +1 -1
  54. data/lib/shadcn_phlexcomponents/components/tabs.rb +1 -1
  55. data/lib/shadcn_phlexcomponents/components/toast_container.rb +1 -1
  56. data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
  57. data/lib/shadcn_phlexcomponents/components/tooltip.rb +3 -2
  58. data/lib/shadcn_phlexcomponents/engine.rb +1 -5
  59. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  60. metadata +9 -4
  61. data/app/javascript/controllers/command_root_controller.ts +0 -355
  62. data/app/javascript/controllers/dropdown_menu_root_controller.ts +0 -234
  63. data/app/javascript/utils.ts +0 -437
@@ -1,6 +1,23 @@
1
- import DropdownMenuRootController from './dropdown_menu_root_controller'
2
-
3
- export default class extends DropdownMenuRootController {
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
+
19
+ const SelectController = class extends Controller<HTMLElement> {
20
+ // targets
4
21
  static targets = [
5
22
  'trigger',
6
23
  'contentContainer',
@@ -8,49 +25,57 @@ export default class extends DropdownMenuRootController {
8
25
  'item',
9
26
  'triggerText',
10
27
  'group',
11
- 'label',
12
28
  'select',
13
29
  ]
30
+ declare readonly triggerTarget: HTMLElement
31
+ declare readonly contentContainerTarget: HTMLElement
32
+ declare readonly contentTarget: HTMLElement
33
+ declare readonly itemTargets: HTMLElement[]
34
+ declare triggerTextTarget: HTMLElement
35
+ declare groupTargets: HTMLElement[]
36
+ declare selectTarget: HTMLSelectElement
14
37
 
38
+ // values
15
39
  static values = {
16
40
  isOpen: Boolean,
17
41
  selected: String,
18
- setEqualWidth: { type: Boolean, default: true },
19
- closestContentSelector: {
20
- type: String,
21
- default: '[data-select-target="content"]',
22
- },
23
42
  }
24
-
43
+ declare isOpenValue: boolean
25
44
  declare selectedValue: string
45
+
46
+ // custom properties
26
47
  declare searchString: string
27
48
  declare searchTimeout: number
28
- declare groupTargets: HTMLElement[]
29
- declare triggerTextTarget: HTMLElement
30
- declare selectTarget: HTMLSelectElement
31
49
  declare itemsInnerText: string[]
50
+ declare items: HTMLElement[]
51
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
52
+ declare cleanup: () => void
32
53
 
33
54
  connect() {
34
- super.connect()
55
+ this.items = getSameLevelItems({
56
+ content: this.contentTarget,
57
+ items: this.itemTargets,
58
+ closestContentSelector: '[data-select-target="content"]',
59
+ })
35
60
  this.itemsInnerText = this.items.map((i) => i.innerText.trim())
36
- this.setAriaLabelledby()
37
61
  this.searchString = ''
62
+ useClickOutside(this, { element: this.contentTarget, dispatchEvent: false })
63
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
64
+ setGroupLabelsId(this)
38
65
  }
39
66
 
40
- setAriaLabelledby() {
41
- this.groupTargets.forEach((g) => {
42
- const label = g.querySelector(
43
- '[data-select-target="label"]',
44
- ) as HTMLElement
45
-
46
- if (label) {
47
- label.id = g.getAttribute('aria-labelledby') as string
48
- }
49
- })
67
+ toggle(event: MouseEvent) {
68
+ if (this.isOpenValue) {
69
+ this.close()
70
+ } else {
71
+ this.open(event)
72
+ }
50
73
  }
51
74
 
52
- onOpenFocusedElement(event: MouseEvent | KeyboardEvent) {
53
- let itemIndex = null as number | null
75
+ open(event: MouseEvent | KeyboardEvent) {
76
+ this.isOpenValue = true
77
+
78
+ let elementToFocus = null as HTMLElement | null
54
79
 
55
80
  if (this.selectedValue) {
56
81
  const item = this.itemTargets.find(
@@ -58,33 +83,161 @@ export default class extends DropdownMenuRootController {
58
83
  )
59
84
 
60
85
  if (item && !item.dataset.disabled) {
61
- itemIndex = this.items.indexOf(item)
86
+ elementToFocus = item
62
87
  }
63
- } else {
88
+ }
89
+
90
+ if (!elementToFocus) {
64
91
  if (event instanceof KeyboardEvent) {
65
92
  const key = event.key
66
93
 
67
94
  if (['ArrowDown', 'Enter', ' '].includes(key)) {
68
- itemIndex = 0
95
+ elementToFocus = this.items[0]
69
96
  }
97
+ } else {
98
+ elementToFocus = this.contentTarget
70
99
  }
71
100
  }
72
101
 
73
- if (itemIndex !== null) {
74
- return this.items[itemIndex]
102
+ focusElement(elementToFocus)
103
+ }
104
+
105
+ close() {
106
+ this.isOpenValue = false
107
+ }
108
+
109
+ onItemFocus(event: FocusEvent) {
110
+ const item = event.currentTarget as HTMLElement
111
+ item.tabIndex = 0
112
+ }
113
+
114
+ onItemBlur(event: FocusEvent) {
115
+ const item = event.currentTarget as HTMLElement
116
+ item.tabIndex = -1
117
+ }
118
+
119
+ focusItemByIndex(
120
+ event: KeyboardEvent | null = null,
121
+ index: number | null = null,
122
+ ) {
123
+ focusItemByIndex(this, event, index)
124
+ }
125
+
126
+ focusItem(event: MouseEvent | KeyboardEvent) {
127
+ const item = event.currentTarget as HTMLElement
128
+ const index = this.items.indexOf(item)
129
+
130
+ if (event instanceof KeyboardEvent) {
131
+ const key = event.key
132
+ let newIndex = 0
133
+
134
+ if (key === 'ArrowUp') {
135
+ newIndex = getPreviousEnabledIndex({
136
+ items: this.items,
137
+ currentIndex: index,
138
+ wrapAround: false,
139
+ })
140
+ } else {
141
+ newIndex = getNextEnabledIndex({
142
+ items: this.items,
143
+ currentIndex: index,
144
+ wrapAround: false,
145
+ })
146
+ }
147
+
148
+ this.items[newIndex].focus()
75
149
  } else {
76
- return this.contentTarget
150
+ // item mouseover event
151
+ this.items[index].focus()
77
152
  }
78
153
  }
79
154
 
80
- onSelect(event: MouseEvent | KeyboardEvent) {
155
+ focusContent() {
156
+ this.contentTarget.focus()
157
+ }
158
+
159
+ select(event: MouseEvent | KeyboardEvent) {
81
160
  const item = event.currentTarget as HTMLElement
82
161
  const value = item.dataset.value as string
83
162
  this.selectedValue = value
163
+ this.close()
164
+ }
165
+
166
+ clickOutside(event: MouseEvent) {
167
+ onClickOutside(this, event)
168
+ }
169
+
170
+ isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
171
+ if (isOpen) {
172
+ lockScroll(this.contentTarget.id)
173
+
174
+ showContent({
175
+ trigger: this.triggerTarget,
176
+ content: this.contentTarget,
177
+ contentContainer: this.contentContainerTarget,
178
+ setEqualWidth: true,
179
+ })
180
+
181
+ this.cleanup = initFloatingUi({
182
+ referenceElement: this.triggerTarget,
183
+ floatingElement: this.contentContainerTarget,
184
+ side: this.contentTarget.dataset.side,
185
+ align: this.contentTarget.dataset.align,
186
+ sideOffset: 4,
187
+ })
188
+
189
+ this.setupEventListeners()
190
+ } else {
191
+ unlockScroll(this.contentTarget.id)
192
+
193
+ hideContent({
194
+ trigger: this.triggerTarget,
195
+ content: this.contentTarget,
196
+ contentContainer: this.contentContainerTarget,
197
+ })
198
+
199
+ if (previousIsOpen) {
200
+ focusTrigger(this.triggerTarget)
201
+ }
202
+
203
+ this.cleanupEventListeners()
204
+ }
84
205
  }
85
206
 
86
- onDOMKeydown(event: KeyboardEvent) {
87
- super.onDOMKeydown(event)
207
+ selectedValueChanged(value: string) {
208
+ const item = this.itemTargets.find((i) => i.dataset.value === value)
209
+
210
+ if (item) {
211
+ this.triggerTextTarget.textContent = item.textContent
212
+
213
+ this.itemTargets.forEach((i) => {
214
+ if (i.dataset.value === value) {
215
+ i.setAttribute('aria-selected', 'true')
216
+ } else {
217
+ i.setAttribute('aria-selected', 'false')
218
+ }
219
+ })
220
+
221
+ this.selectTarget.value = value
222
+ }
223
+
224
+ this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`
225
+
226
+ const placeholder = this.triggerTarget.dataset.placeholder
227
+
228
+ if (placeholder && this.triggerTarget.dataset.hasValue === 'false') {
229
+ this.triggerTextTarget.textContent = placeholder
230
+ }
231
+ }
232
+
233
+ disconnect() {
234
+ this.cleanupEventListeners()
235
+ }
236
+
237
+ protected onDOMKeydown(event: KeyboardEvent) {
238
+ if (!this.isOpenValue) return
239
+
240
+ onKeydown(this, event)
88
241
 
89
242
  const { key, altKey, ctrlKey, metaKey } = event
90
243
 
@@ -97,8 +250,17 @@ export default class extends DropdownMenuRootController {
97
250
  }
98
251
  }
99
252
 
253
+ protected setupEventListeners() {
254
+ document.addEventListener('keydown', this.DOMKeydownListener)
255
+ }
256
+
257
+ protected cleanupEventListeners() {
258
+ if (this.cleanup) this.cleanup()
259
+ document.removeEventListener('keydown', this.DOMKeydownListener)
260
+ }
261
+
100
262
  // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
101
- handleSearch(char: string) {
263
+ protected handleSearch(char: string) {
102
264
  const searchString = this.getSearchString(char)
103
265
  const focusedItem = this.items.find(
104
266
  (item) => document.activeElement === item,
@@ -117,14 +279,14 @@ export default class extends DropdownMenuRootController {
117
279
  }
118
280
  }
119
281
 
120
- filterItemsInnerText(items: string[], filter: string) {
282
+ protected filterItemsInnerText(items: string[], filter: string) {
121
283
  return items.filter((item) => {
122
284
  const matches = item.toLowerCase().indexOf(filter.toLowerCase()) === 0
123
285
  return matches
124
286
  })
125
287
  }
126
288
 
127
- getSearchString(char: string) {
289
+ protected getSearchString(char: string) {
128
290
  // reset typing timeout and start new timeout
129
291
  // this allows us to make multiple-letter matches, like a native select
130
292
  if (typeof this.searchTimeout === 'number') {
@@ -142,7 +304,7 @@ export default class extends DropdownMenuRootController {
142
304
 
143
305
  // return the index of an option from an array of options, based on a search string
144
306
  // if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
145
- getIndexByLetter(filter: string, startIndex: number) {
307
+ protected getIndexByLetter(filter: string, startIndex: number) {
146
308
  const orderedItems = [
147
309
  ...this.itemsInnerText.slice(startIndex),
148
310
  ...this.itemsInnerText.slice(0, startIndex),
@@ -171,30 +333,9 @@ export default class extends DropdownMenuRootController {
171
333
  return -1
172
334
  }
173
335
  }
336
+ }
174
337
 
175
- selectedValueChanged(value: string) {
176
- const item = this.itemTargets.find((i) => i.dataset.value === value)
177
-
178
- if (item) {
179
- this.triggerTextTarget.textContent = item.textContent
180
-
181
- this.itemTargets.forEach((i) => {
182
- if (i.dataset.value === value) {
183
- i.setAttribute('aria-selected', 'true')
184
- } else {
185
- i.setAttribute('aria-selected', 'false')
186
- }
187
- })
188
-
189
- this.selectTarget.value = value
190
- }
191
-
192
- this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`
338
+ type Select = InstanceType<typeof SelectController>
193
339
 
194
- const placeholder = this.triggerTarget.dataset.placeholder
195
-
196
- if (placeholder && this.triggerTarget.dataset.hasValue === 'false') {
197
- this.triggerTextTarget.textContent = placeholder
198
- }
199
- }
200
- }
340
+ export { SelectController }
341
+ export type { Select }
@@ -1,17 +1,19 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import noUiSlider, { API, Options } from 'nouislider'
3
3
 
4
- export default class extends Controller<HTMLElement> {
4
+ const SliderController = class extends Controller<HTMLElement> {
5
+ // targets
5
6
  static targets = ['slider', 'hiddenInput', 'endHiddenInput']
6
-
7
7
  declare readonly sliderTarget: HTMLInputElement
8
8
  declare readonly hiddenInputTarget: HTMLInputElement
9
9
  declare readonly endHiddenInputTarget: HTMLInputElement
10
10
  declare readonly hasEndHiddenInputTarget: boolean
11
- declare onUpdateValuesListener: (values: (string | number)[]) => void
12
- declare DOMClickListener: (event: MouseEvent) => void
11
+
12
+ // custom properties
13
13
  declare range: boolean
14
14
  declare slider: API
15
+ declare onUpdateValuesListener: (values: (string | number)[]) => void
16
+ declare DOMClickListener: (event: MouseEvent) => void
15
17
 
16
18
  connect() {
17
19
  this.range = this.element.dataset.range === 'true'
@@ -35,15 +37,11 @@ export default class extends Controller<HTMLElement> {
35
37
  document.addEventListener('click', this.DOMClickListener)
36
38
  }
37
39
 
38
- onUpdateValues(values: (string | number)[]) {
39
- this.hiddenInputTarget.value = `${values[0]}`
40
-
41
- if (this.range && this.hasEndHiddenInputTarget) {
42
- this.endHiddenInputTarget.value = `${values[1]}`
43
- }
40
+ disconnect() {
41
+ document.removeEventListener('click', this.DOMClickListener)
44
42
  }
45
43
 
46
- getOptions() {
44
+ protected getOptions() {
47
45
  const defaultOptions = {
48
46
  connect: this.range ? true : 'lower',
49
47
  tooltips: true,
@@ -84,7 +82,15 @@ export default class extends Controller<HTMLElement> {
84
82
  }
85
83
  }
86
84
 
87
- onDOMClick(event: MouseEvent) {
85
+ protected onUpdateValues(values: (string | number)[]) {
86
+ this.hiddenInputTarget.value = `${values[0]}`
87
+
88
+ if (this.range && this.hasEndHiddenInputTarget) {
89
+ this.endHiddenInputTarget.value = `${values[1]}`
90
+ }
91
+ }
92
+
93
+ protected onDOMClick(event: MouseEvent) {
88
94
  const target = event.target
89
95
 
90
96
  // Focus handle of slider when label is clicked.
@@ -100,8 +106,9 @@ export default class extends Controller<HTMLElement> {
100
106
  }
101
107
  }
102
108
  }
103
-
104
- disconnect() {
105
- document.removeEventListener('click', this.DOMClickListener)
106
- }
107
109
  }
110
+
111
+ type Slider = InstanceType<typeof SliderController>
112
+
113
+ export { SliderController }
114
+ export type { Slider }
@@ -1,13 +1,15 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
 
3
- export default class extends Controller<HTMLElement> {
3
+ const SwitchController = class extends Controller<HTMLElement> {
4
+ // targets
4
5
  static targets = ['input', 'thumb']
6
+ declare readonly inputTarget: HTMLInputElement
7
+ declare readonly thumbTarget: HTMLElement
8
+
9
+ // values
5
10
  static values = {
6
11
  isChecked: Boolean,
7
12
  }
8
-
9
- declare readonly inputTarget: HTMLInputElement
10
- declare readonly thumbTarget: HTMLElement
11
13
  declare isCheckedValue: boolean
12
14
 
13
15
  toggle() {
@@ -28,3 +30,8 @@ export default class extends Controller<HTMLElement> {
28
30
  }
29
31
  }
30
32
  }
33
+
34
+ type Switch = InstanceType<typeof SwitchController>
35
+
36
+ export { SwitchController }
37
+ export type { Switch }
@@ -1,18 +1,21 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
+ import { getNextEnabledIndex, getPreviousEnabledIndex } from '../utils'
2
3
 
3
- export default class extends Controller {
4
+ const TabsController = class extends Controller {
5
+ // targets
4
6
  static targets = ['trigger', 'content']
7
+ declare readonly triggerTargets: HTMLButtonElement[]
8
+ declare readonly contentTargets: HTMLElement[]
9
+
10
+ // values
5
11
  static values = {
6
12
  active: String,
7
13
  }
8
-
9
- declare readonly triggerTargets: HTMLButtonElement[]
10
- declare readonly contentTargets: HTMLElement[]
11
- declare activeValue: string | undefined
14
+ declare activeValue: string
12
15
 
13
16
  connect() {
14
17
  if (!this.activeValue) {
15
- this.activeValue = this.triggerTargets[0].dataset.value
18
+ this.activeValue = this.triggerTargets[0].dataset.value as string
16
19
  }
17
20
  }
18
21
 
@@ -20,7 +23,7 @@ export default class extends Controller {
20
23
  const target = event.currentTarget as HTMLButtonElement
21
24
 
22
25
  if (event instanceof MouseEvent) {
23
- this.activeValue = target.dataset.value
26
+ this.activeValue = target.dataset.value as string
24
27
  } else {
25
28
  const key = event.key
26
29
 
@@ -32,20 +35,20 @@ export default class extends Controller {
32
35
  let newIndex = 0
33
36
 
34
37
  if (key === 'ArrowLeft') {
35
- newIndex = index - 1
36
-
37
- if (newIndex < 0) {
38
- newIndex = focusableTriggers.length - 1
39
- }
38
+ newIndex = getPreviousEnabledIndex({
39
+ items: focusableTriggers,
40
+ currentIndex: index,
41
+ wrapAround: true,
42
+ })
40
43
  } else {
41
- newIndex = index + 1
42
-
43
- if (newIndex > focusableTriggers.length - 1) {
44
- newIndex = 0
45
- }
44
+ newIndex = getNextEnabledIndex({
45
+ items: focusableTriggers,
46
+ currentIndex: index,
47
+ wrapAround: true,
48
+ })
46
49
  }
47
50
 
48
- this.activeValue = focusableTriggers[newIndex].dataset.value
51
+ this.activeValue = focusableTriggers[newIndex].dataset.value as string
49
52
  focusableTriggers[newIndex].focus()
50
53
  }
51
54
  }
@@ -77,3 +80,8 @@ export default class extends Controller {
77
80
  })
78
81
  }
79
82
  }
83
+
84
+ type Tabs = InstanceType<typeof TabsController>
85
+
86
+ export { TabsController }
87
+ export type { Tabs }
@@ -1,6 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
 
3
- export default class extends Controller {
3
+ const ThemeSwitcherController = class extends Controller {
4
4
  initialize() {
5
5
  if (
6
6
  localStorage.theme === 'dark' ||
@@ -33,3 +33,8 @@ export default class extends Controller {
33
33
  document.documentElement.style.colorScheme = 'dark'
34
34
  }
35
35
  }
36
+
37
+ type ThemeSwitcher = InstanceType<typeof ThemeSwitcherController>
38
+
39
+ export { ThemeSwitcherController }
40
+ export type { ThemeSwitcher }
@@ -1,7 +1,7 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import DOMPurify from 'dompurify'
3
3
 
4
- export default class extends Controller<HTMLElement> {
4
+ const ToastContainerController = class extends Controller<HTMLElement> {
5
5
  addToast({
6
6
  title,
7
7
  description,
@@ -60,3 +60,8 @@ export default class extends Controller<HTMLElement> {
60
60
  this.element.append(clone)
61
61
  }
62
62
  }
63
+
64
+ type ToastContainer = InstanceType<typeof ToastContainerController>
65
+
66
+ export { ToastContainerController }
67
+ export type { ToastContainer }
@@ -1,7 +1,8 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import { ANIMATION_OUT_DELAY } from '../utils'
3
3
 
4
- export default class extends Controller<HTMLElement> {
4
+ const ToastController = class extends Controller<HTMLElement> {
5
+ // custom properties
5
6
  declare duration: number
6
7
  declare closeTimeout: number
7
8
 
@@ -26,3 +27,8 @@ export default class extends Controller<HTMLElement> {
26
27
  }
27
28
  }
28
29
  }
30
+
31
+ type Toast = InstanceType<typeof ToastController>
32
+
33
+ export { ToastController }
34
+ export type { Toast }
@@ -0,0 +1,28 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ const ToggleController = class extends Controller<HTMLElement> {
4
+ // values
5
+ static values = {
6
+ isOn: Boolean,
7
+ }
8
+ declare isOnValue: boolean
9
+
10
+ toggle() {
11
+ this.isOnValue = !this.isOnValue
12
+ }
13
+
14
+ isOnValueChanged(isOn: boolean) {
15
+ if (isOn) {
16
+ this.element.dataset.state = 'on'
17
+ this.element.ariaPressed = 'true'
18
+ } else {
19
+ this.element.dataset.state = 'off'
20
+ this.element.ariaPressed = 'false'
21
+ }
22
+ }
23
+ }
24
+
25
+ type Toggle = InstanceType<typeof ToggleController>
26
+
27
+ export { ToggleController }
28
+ export type { Toggle }
@@ -0,0 +1,28 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ const ToggleController = class extends Controller<HTMLElement> {
4
+ // values
5
+ static values = {
6
+ isOn: Boolean,
7
+ }
8
+ declare isOnValue: boolean
9
+
10
+ toggle() {
11
+ this.isOnValue = !this.isOnValue
12
+ }
13
+
14
+ isOnValueChanged(isOn: boolean) {
15
+ if (isOn) {
16
+ this.element.dataset.state = 'on'
17
+ this.element.ariaPressed = 'true'
18
+ } else {
19
+ this.element.dataset.state = 'off'
20
+ this.element.ariaPressed = 'false'
21
+ }
22
+ }
23
+ }
24
+
25
+ type Toggle = InstanceType<typeof ToggleController>
26
+
27
+ export { ToggleController }
28
+ export type { Toggle }