odt-ui-rails 2.2.4 → 2.2.5

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.
@@ -10,7 +10,6 @@ module Odt
10
10
  desc "Configures ODT UI standalone stylesheets, Tailwind theme, and RBS type signatures for Rails application"
11
11
 
12
12
  def setup_stylesheets
13
- # 1. Setup Tailwind CSS v4 Theme if Tailwind is present
14
13
  tailwind_candidates = [
15
14
  "app/assets/tailwind/application.css",
16
15
  "app/assets/stylesheets/application.tailwind.css"
@@ -29,12 +28,10 @@ module Odt
29
28
  end
30
29
  end
31
30
 
32
- # 2. Inject stylesheet_link_tag into application.html.erb layout
33
31
  layout_file = "app/views/layouts/application.html.erb"
34
32
  if File.exist?(layout_file)
35
33
  layout_content = File.read(layout_file)
36
34
  unless layout_content.include?("odt_ui")
37
- # Inject right after stylesheet_link_tag :app or in <head>
38
35
  if layout_content.include?("<%= stylesheet_link_tag :app")
39
36
  insert_into_file layout_file, "\n <%= stylesheet_link_tag \"odt_ui\", \"data-turbo-track\": \"reload\" %>", after: /<%= stylesheet_link_tag :app[^\n]*%>/
40
37
  elsif layout_content.include?("<%= stylesheet_link_tag \"application\"")
@@ -56,8 +53,10 @@ module Odt
56
53
  def copy_stimulus_controller
57
54
  if File.directory?("app/javascript/controllers") || File.directory?("app/javascript")
58
55
  empty_directory "app/javascript/controllers"
56
+ copy_file "odt_toaster_controller.js", "app/javascript/controllers/odt_toaster_controller.js"
59
57
  copy_file "odt_toast_controller.js", "app/javascript/controllers/odt_toast_controller.js"
60
58
  copy_file "odt_modal_controller.js", "app/javascript/controllers/odt_modal_controller.js"
59
+ copy_file "odt_dropdown_controller.js", "app/javascript/controllers/odt_dropdown_controller.js"
61
60
  say "Copied Stimulus controllers to app/javascript/controllers/", :green
62
61
  end
63
62
  end
@@ -0,0 +1,137 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["trigger", "content", "chevron", "hiddenInput", "text"]
5
+ static values = {
6
+ open: { type: Boolean, default: false },
7
+ closeOnClickOutside: { type: Boolean, default: true },
8
+ closeOnSelect: { type: Boolean, default: true }
9
+ }
10
+
11
+ connect() {
12
+ this.boundPointerDown = (event) => {
13
+ if (!this.openValue || !this.closeOnClickOutsideValue) return
14
+ if (!this.element.contains(event.target)) {
15
+ this.close()
16
+ }
17
+ }
18
+ document.addEventListener("pointerdown", this.boundPointerDown)
19
+
20
+ this.boundKeyDown = (event) => {
21
+ if (!this.openValue) return
22
+ if (event.key === "Escape") {
23
+ event.stopPropagation()
24
+ this.close()
25
+ if (this.hasTriggerTarget) this.triggerTarget.focus()
26
+ }
27
+ }
28
+ window.addEventListener("keydown", this.boundKeyDown)
29
+
30
+ if (this.openValue) {
31
+ this.open()
32
+ }
33
+ }
34
+
35
+ disconnect() {
36
+ if (this.boundPointerDown) {
37
+ document.removeEventListener("pointerdown", this.boundPointerDown)
38
+ }
39
+ if (this.boundKeyDown) {
40
+ window.removeEventListener("keydown", this.boundKeyDown)
41
+ }
42
+ }
43
+
44
+ toggle(event) {
45
+ if (event) event.preventDefault()
46
+ if (this.openValue) {
47
+ this.close()
48
+ } else {
49
+ this.open()
50
+ }
51
+ }
52
+
53
+ open(event) {
54
+ if (event) event.preventDefault()
55
+ this.openValue = true
56
+
57
+ if (this.hasContentTarget) {
58
+ this.contentTarget.classList.remove("hidden")
59
+ this.contentTarget.classList.remove("odt-dropdown__content--exiting")
60
+ }
61
+
62
+ if (this.hasTriggerTarget) {
63
+ this.triggerTarget.classList.add("odt-dropdown__trigger--open")
64
+ this.triggerTarget.setAttribute("aria-expanded", "true")
65
+ }
66
+
67
+ if (this.hasChevronTarget) {
68
+ this.chevronTarget.classList.add("odt-dropdown__chevron--open")
69
+ }
70
+
71
+ this.element.dispatchEvent(new CustomEvent("odt-dropdown:opened", { bubbles: true }))
72
+ }
73
+
74
+ close(event) {
75
+ if (event) event.preventDefault()
76
+ this.openValue = false
77
+
78
+ if (this.hasTriggerTarget) {
79
+ this.triggerTarget.classList.remove("odt-dropdown__trigger--open")
80
+ this.triggerTarget.setAttribute("aria-expanded", "false")
81
+ }
82
+
83
+ if (this.hasChevronTarget) {
84
+ this.chevronTarget.classList.remove("odt-dropdown__chevron--open")
85
+ }
86
+
87
+ if (this.hasContentTarget) {
88
+ this.contentTarget.classList.add("odt-dropdown__content--exiting")
89
+ setTimeout(() => {
90
+ this.contentTarget.classList.add("hidden")
91
+ this.contentTarget.classList.remove("odt-dropdown__content--exiting")
92
+ this.element.dispatchEvent(new CustomEvent("odt-dropdown:closed", { bubbles: true }))
93
+ }, 110)
94
+ }
95
+ }
96
+
97
+ select(event) {
98
+ const item = event.currentTarget
99
+ const value = item.dataset.value
100
+ const label = item.dataset.label || item.textContent.trim()
101
+
102
+ if (this.hasHiddenInputTarget && value !== undefined) {
103
+ this.hiddenInputTarget.value = value
104
+ this.hiddenInputTarget.dispatchEvent(new Event("change", { bubbles: true }))
105
+ }
106
+
107
+ if (this.hasTextTarget && label) {
108
+ this.textTarget.textContent = label
109
+ this.textTarget.classList.remove("odt-dropdown__placeholder")
110
+ }
111
+
112
+ if (this.closeOnSelectValue) {
113
+ this.close()
114
+ }
115
+ }
116
+
117
+ navigate(event) {
118
+ if (!this.hasContentTarget || !this.openValue) return
119
+
120
+ const items = Array.from(
121
+ this.contentTarget.querySelectorAll('[role="menuitem"]:not([disabled]):not(.odt-dropdown__item--disabled)')
122
+ )
123
+ if (!items.length) return
124
+
125
+ const activeIndex = items.indexOf(document.activeElement)
126
+
127
+ if (event.key === "ArrowDown") {
128
+ event.preventDefault()
129
+ const nextIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0
130
+ items[nextIndex]?.focus()
131
+ } else if (event.key === "ArrowUp") {
132
+ event.preventDefault()
133
+ const prevIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1
134
+ items[prevIndex]?.focus()
135
+ }
136
+ }
137
+ }
@@ -8,6 +8,24 @@ export default class extends Controller {
8
8
  }
9
9
 
10
10
  connect() {
11
+ if (this.element.id) {
12
+ this.boundTriggerHandler = (event) => {
13
+ const trigger = event.target.closest(`[data-odt-modal-target-id="${this.element.id}"], [data-modal-target="${this.element.id}"], [href="#${this.element.id}"]`)
14
+ if (trigger) {
15
+ event.preventDefault()
16
+ this.open()
17
+ }
18
+ }
19
+ document.addEventListener("click", this.boundTriggerHandler)
20
+ }
21
+
22
+ this.boundTurboSubmitHandler = (event) => {
23
+ if (event.detail?.success && this.element.contains(event.target)) {
24
+ this.close()
25
+ }
26
+ }
27
+ this.element.addEventListener("turbo:submit-end", this.boundTurboSubmitHandler)
28
+
11
29
  if (this.openValue) {
12
30
  this.open()
13
31
  }
@@ -57,5 +75,11 @@ export default class extends Controller {
57
75
 
58
76
  disconnect() {
59
77
  document.body.style.overflow = ""
78
+ if (this.boundTriggerHandler) {
79
+ document.removeEventListener("click", this.boundTriggerHandler)
80
+ }
81
+ if (this.boundTurboSubmitHandler) {
82
+ this.element.removeEventListener("turbo:submit-end", this.boundTurboSubmitHandler)
83
+ }
60
84
  }
61
85
  }
@@ -7,6 +7,7 @@ export default class extends Controller {
7
7
 
8
8
  connect() {
9
9
  this.startTimer()
10
+ this.notifyToaster()
10
11
  }
11
12
 
12
13
  startTimer() {
@@ -26,11 +27,22 @@ export default class extends Controller {
26
27
  this.startTimer()
27
28
  }
28
29
 
30
+ notifyToaster() {
31
+ const toaster = this.element.closest("[data-controller~='odt-toaster']")
32
+ if (toaster) {
33
+ const toasterController = this.application.getControllerForElementAndIdentifier(toaster, "odt-toaster")
34
+ toasterController?.updateStack()
35
+ }
36
+ }
37
+
29
38
  dismiss() {
30
39
  this.pauseTimer()
31
40
  this.element.classList.add("odt-toast--exiting")
41
+ this.notifyToaster()
42
+
32
43
  setTimeout(() => {
33
44
  this.element.remove()
45
+ this.notifyToaster()
34
46
  }, 250)
35
47
  }
36
48
 
@@ -0,0 +1,114 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ position: { type: String, default: "top-right" },
6
+ expand: { type: Boolean, default: true }
7
+ }
8
+
9
+ connect() {
10
+ this.isHovered = false
11
+ this.leaveTimeout = null
12
+ this.mutationObserver = new MutationObserver(() => this.handleMutations())
13
+ this.mutationObserver.observe(this.element, { childList: true })
14
+ this.handleMutations()
15
+ }
16
+
17
+ disconnect() {
18
+ if (this.mutationObserver) {
19
+ this.mutationObserver.disconnect()
20
+ }
21
+ if (this.leaveTimeout) {
22
+ clearTimeout(this.leaveTimeout)
23
+ }
24
+ }
25
+
26
+ handleMutations() {
27
+ const unmountedToasts = this.element.querySelectorAll(":scope > .odt-toast:not([data-toast-mounted]):not(.odt-toast--exiting)")
28
+ const isTop = this.positionValue.startsWith("top")
29
+
30
+ unmountedToasts.forEach((toast) => {
31
+ toast.style.transform = `translate3d(0, ${isTop ? -24 : 24}px, 0) scale(0.92)`
32
+ toast.style.opacity = "0"
33
+ toast.style.pointerEvents = "none"
34
+ toast.dataset.toastMounted = "pending"
35
+ })
36
+
37
+ requestAnimationFrame(() => {
38
+ requestAnimationFrame(() => {
39
+ unmountedToasts.forEach((toast) => {
40
+ toast.dataset.toastMounted = "true"
41
+ })
42
+ this.updateStack()
43
+ })
44
+ })
45
+ }
46
+
47
+ hover() {
48
+ if (!this.expandValue) return
49
+ if (this.leaveTimeout) {
50
+ clearTimeout(this.leaveTimeout)
51
+ this.leaveTimeout = null
52
+ }
53
+ if (!this.isHovered) {
54
+ this.isHovered = true
55
+ this.element.style.pointerEvents = "auto"
56
+ this.updateStack()
57
+ }
58
+ }
59
+
60
+ leave() {
61
+ if (!this.expandValue) return
62
+ if (this.leaveTimeout) {
63
+ clearTimeout(this.leaveTimeout)
64
+ }
65
+ this.leaveTimeout = setTimeout(() => {
66
+ this.isHovered = false
67
+ this.element.style.pointerEvents = "none"
68
+ this.updateStack()
69
+ }, 180)
70
+ }
71
+
72
+ get toasts() {
73
+ return Array.from(this.element.querySelectorAll(":scope > .odt-toast:not(.odt-toast--exiting)"))
74
+ }
75
+
76
+ updateStack() {
77
+ const toasts = this.toasts.reverse()
78
+ const isTop = this.positionValue.startsWith("top")
79
+ const GAP = 12
80
+
81
+ let accumulatedOffset = 0
82
+
83
+ toasts.forEach((toast, index) => {
84
+ if (toast.dataset.toastMounted === "pending") return
85
+
86
+ const isVisible = index < 3 || this.isHovered
87
+ const zIndex = 50 - index
88
+ const stackedBaseY = isTop ? index * 14 : -(index * 14)
89
+ const stackedBaseScale = Math.max(0.84, 1 - index * 0.06)
90
+
91
+ let targetY = 0
92
+ let targetScale = 1
93
+ let targetOpacity = 1
94
+
95
+ if (this.isHovered) {
96
+ targetY = isTop ? accumulatedOffset : -accumulatedOffset
97
+ targetScale = 1
98
+ targetOpacity = isVisible ? 1 : 0
99
+ } else {
100
+ targetY = stackedBaseY
101
+ targetScale = stackedBaseScale
102
+ targetOpacity = index === 0 ? 1 : index === 1 ? 0.92 : index === 2 ? 0.75 : 0
103
+ }
104
+
105
+ toast.style.transform = `translate3d(0, ${targetY}px, 0) scale(${targetScale})`
106
+ toast.style.opacity = targetOpacity
107
+ toast.style.zIndex = zIndex
108
+ toast.style.pointerEvents = isVisible ? "auto" : "none"
109
+
110
+ const height = toast.offsetHeight || 64
111
+ accumulatedOffset += height + GAP
112
+ })
113
+ }
114
+ }
@@ -2,41 +2,45 @@ module OdtUi
2
2
  VERSION: String
3
3
 
4
4
  module Helper
5
- type button_variant = :filled | :frosted | :capsule | :ghost | :light | :outline | :outlined | :subtle | String
6
- type button_color = :primary | :secondary | :surface | :muted | :inverse | :success | :warning | :danger | :info | :neutral | String
7
- type button_size = :xs | :sm | :md | :lg | :xl | String
8
- type radius_size = :none | :xs | :sm | :md | :lg | :xl | :"2xl" | :"3xl" | :"4xl" | :full | Symbol | String
9
- type shadow_size = :none | :card | :dropdown | :modal | :popover | Symbol | String
10
-
11
- type badge_variant = :subtle | :filled | :outlined | :outline | String
12
- type badge_color = :primary | :secondary | :success | :warning | :danger | :info | :neutral | String
13
- type badge_size = :sm | :md | String
14
-
15
- type card_variant = :elevated | :outlined | :subtle | :muted | :ghost | :frosted | String
16
- type card_background = :base | :surface | :surface_elevated | :surface_muted | :surface_subtle | :gradient_primary | :gradient_secondary | :gradient_vibrant | :gradient_hero | :frosted | :frosted_muted | :frosted_subtle | :frosted_gradient_primary | :frosted_gradient_secondary | :transparent | Symbol | String
17
- type card_border = :none | :muted | :base | :strong | :primary | :secondary | :danger | :success | bool | Symbol | String
18
- type card_padding = :none | :xs | :sm | :md | :lg | :xl | :"2xl" | Symbol | String
19
- type card_tag = :div | :article | :section | :aside | :main | Symbol | String
20
-
21
- type alert_variant = :success | :warning | :danger | :info | String
22
- type heading_tag = :h1 | :h2 | :h3 | :h4 | :h5 | :h6 | String
23
- type heading_size = :"5xl" | :"4xl" | :"3xl" | :"2xl" | :xl | :lg | Symbol | String
24
- type font_weight = :light | :normal | :medium | :semibold | :bold | :extrabold | Symbol | String
25
- type typography_color = :strong | :default | :muted | :subtle | :disabled | :primary | :secondary | :success | :warning | :danger | :info | Symbol | String
26
-
27
- type text_tag = :p | :span | :div | :label | :small | Symbol | String
28
- type text_size = :xs | :sm | :base | :lg | :xl | :"2xl" | Symbol | String
29
-
30
- type avatar_size = :xs | :sm | :md | :lg | :xl | :"2xl" | String
31
- type avatar_color = :secondary | :primary | :neutral | String
32
-
33
- type input_variant = :outlined | :filled | :frosted | String
34
- type input_size = :sm | :md | :lg | String
35
- type input_resize = :none | :vertical | :horizontal | :both | String
36
- type checkbox_color = :primary | :secondary | :success | :warning | :danger | :dark | :neutral | String
37
- type checkbox_size = :sm | :md | :lg | String
38
-
39
- # Renders an ODT Button component.
5
+ type button_variant = :filled | :frosted | :capsule | :ghost
6
+ type button_color = :primary | :secondary | :surface | :muted | :inverse | :success | :warning | :danger | :info
7
+ type button_size = :sm | :md | :lg
8
+ type radius_size = :none | :xs | :sm | :md | :lg | :xl | :"2xl" | :"3xl" | :"4xl" | :full
9
+ type shadow_size = :none | :card | :dropdown | :modal | :popover
10
+
11
+ type badge_variant = :subtle | :filled | :outlined
12
+ type badge_color = :primary | :secondary | :success | :warning | :danger | :info | :neutral
13
+ type badge_size = :sm | :md
14
+
15
+ type card_variant = :elevated | :outlined | :subtle | :ghost
16
+ type card_background = :base | :surface | :surface_elevated | :"surface-elevated" | :surface_muted | :"surface-muted" | :surface_subtle | :"surface-subtle" | :gradient_primary | :"gradient-primary" | :gradient_secondary | :"gradient-secondary" | :gradient_vibrant | :"gradient-vibrant" | :gradient_hero | :"gradient-hero" | :frosted | :frosted_muted | :"frosted-muted" | :frosted_subtle | :"frosted-subtle" | :frosted_gradient_primary | :"frosted-gradient-primary" | :frosted_gradient_secondary | :"frosted-gradient-secondary" | :transparent
17
+ type card_border = :none | :muted | :base | :strong | :primary | :secondary | :danger | :success | bool
18
+ type card_padding = :none | :xs | :sm | :md | :lg | :xl | :"2xl"
19
+ type card_tag = :div | :article | :section | :aside | :main
20
+
21
+ type alert_variant = :success | :warning | :danger | :info
22
+
23
+ type heading_tag = :h1 | :h2 | :h3 | :h4 | :h5 | :h6
24
+ type heading_size = :"5xl" | :"4xl" | :"3xl" | :"2xl" | :xl | :lg
25
+ type font_weight = :light | :normal | :medium | :semibold | :bold | :extrabold
26
+ type typography_color = :strong | :default | :muted | :subtle | :disabled | :inverse | :"inverse-strong" | :inverse_strong | :"inverse-muted" | :inverse_muted | :"inverse-subtle" | :inverse_subtle | :"inverse-disabled" | :inverse_disabled | :primary | :secondary | :success | :warning | :danger | :info
27
+
28
+ type text_tag = :p | :span | :div | :label | :small
29
+ type text_size = :xs | :sm | :base | :lg | :xl | :"2xl"
30
+
31
+ type avatar_size = :xs | :sm | :md | :lg | :xl | :"2xl"
32
+ type avatar_color = :primary | :secondary | :neutral
33
+
34
+ type input_variant = :outlined | :filled | :frosted | :unstyled
35
+ type input_size = :sm | :md | :lg
36
+ type input_resize = :none | :vertical | :horizontal | :both
37
+
38
+ type checkbox_color = :primary | :secondary | :success | :warning | :danger | :dark
39
+ type checkbox_size = :sm | :md | :lg
40
+ type radio_color = :primary | :secondary | :success | :warning | :danger | :dark
41
+ type radio_size = :sm | :md | :lg
42
+ type radio_orientation = :horizontal | :vertical
43
+
40
44
  def odt_button: (
41
45
  ?String? text,
42
46
  ?variant: button_variant,
@@ -55,7 +59,6 @@ module OdtUi
55
59
  **untyped
56
60
  ) ?{ () -> untyped } -> untyped
57
61
 
58
- # Renders an ODT Badge component.
59
62
  def odt_badge: (
60
63
  ?String? text,
61
64
  ?variant: badge_variant,
@@ -67,7 +70,6 @@ module OdtUi
67
70
  **untyped
68
71
  ) ?{ () -> untyped } -> untyped
69
72
 
70
- # Renders an ODT Card component.
71
73
  def odt_card: (
72
74
  ?title: String?,
73
75
  ?subtitle: String?,
@@ -83,7 +85,6 @@ module OdtUi
83
85
  **untyped
84
86
  ) ?{ () -> untyped } -> untyped
85
87
 
86
- # Renders an ODT Alert banner.
87
88
  def odt_alert: (
88
89
  ?String? message,
89
90
  ?variant: alert_variant,
@@ -91,7 +92,6 @@ module OdtUi
91
92
  **untyped
92
93
  ) ?{ () -> untyped } -> untyped
93
94
 
94
- # Renders an ODT Heading element.
95
95
  def odt_heading: (
96
96
  ?String? text,
97
97
  ?as: heading_tag,
@@ -102,7 +102,6 @@ module OdtUi
102
102
  **untyped
103
103
  ) ?{ () -> untyped } -> untyped
104
104
 
105
- # Renders styled body text.
106
105
  def odt_text: (
107
106
  ?String? text,
108
107
  ?as: text_tag,
@@ -113,18 +112,16 @@ module OdtUi
113
112
  **untyped
114
113
  ) ?{ () -> untyped } -> untyped
115
114
 
116
- # Renders an ODT Avatar element.
117
115
  def odt_avatar: (
118
116
  ?name: String?,
119
117
  ?src: String?,
120
118
  ?size: avatar_size,
121
119
  ?color: avatar_color,
122
- ?icon: String,
120
+ ?icon: String?,
123
121
  ?fallback: String?,
124
122
  **untyped
125
123
  ) -> untyped
126
124
 
127
- # Renders a styled input field.
128
125
  def odt_input: (
129
126
  ?label: String?,
130
127
  ?variant: input_variant,
@@ -147,7 +144,6 @@ module OdtUi
147
144
  **untyped
148
145
  ) -> untyped
149
146
 
150
- # Renders a styled textarea field.
151
147
  def odt_textarea: (
152
148
  ?label: String?,
153
149
  ?variant: input_variant,
@@ -164,7 +160,6 @@ module OdtUi
164
160
  **untyped
165
161
  ) ?{ () -> untyped } -> untyped
166
162
 
167
- # Renders a styled native <select> dropdown.
168
163
  def odt_select: (
169
164
  ?label: String?,
170
165
  ?options: untyped,
@@ -181,7 +176,6 @@ module OdtUi
181
176
  **untyped
182
177
  ) -> untyped
183
178
 
184
- # Renders a styled checkbox.
185
179
  def odt_checkbox: (
186
180
  ?label: String?,
187
181
  ?description: String?,
@@ -194,19 +188,17 @@ module OdtUi
194
188
  **untyped
195
189
  ) -> untyped
196
190
 
197
- # Renders a styled radio button.
198
191
  def odt_radio: (
199
192
  ?label: String?,
200
193
  ?description: String?,
201
194
  ?checked: bool,
202
- ?color: checkbox_color,
203
- ?size: checkbox_size,
195
+ ?color: radio_color,
196
+ ?size: radio_size,
204
197
  ?disabled: bool,
205
198
  ?error: String?,
206
199
  **untyped
207
200
  ) -> untyped
208
201
 
209
- # Renders a form field wrapper.
210
202
  def odt_form_group: (
211
203
  ?label: String?,
212
204
  ?error: String?,
@@ -217,48 +209,49 @@ module OdtUi
217
209
  **untyped
218
210
  ) ?{ () -> untyped } -> untyped
219
211
 
220
- type toaster_position = :top_right | :top_center | :top_left | :bottom_right | :bottom_center | :bottom_left | Symbol | String
221
- type toast_variant = :success | :warning | :danger | :error | :info | :loading | :frosted | Symbol | String
212
+ type toaster_position = :top_right | :"top-right" | :top_center | :"top-center" | :top_left | :"top-left" | :bottom_right | :"bottom-right" | :bottom_center | :"bottom-center" | :bottom_left | :"bottom-left"
213
+ type toast_variant = :elevated | :frosted | :subtle | :outlined
214
+ type toast_type = :default | :success | :warning | :danger | :error | :info | :loading
222
215
 
223
- # Renders an ODT Toaster viewport container.
224
216
  def odt_toaster: (
225
217
  ?position: toaster_position,
226
218
  ?id: String,
219
+ ?expand: bool,
227
220
  **untyped
228
221
  ) ?{ () -> untyped } -> untyped
229
222
 
230
- # Renders a single ODT Toast card.
231
223
  def odt_toast: (
232
224
  ?String? message,
233
225
  ?title: String?,
234
226
  ?variant: toast_variant,
227
+ ?type: toast_type,
235
228
  ?icon: String?,
236
229
  ?duration: Integer,
237
230
  ?dismissible: bool,
238
231
  ?radius: radius_size?,
232
+ ?rich_colors: bool,
239
233
  **untyped
240
234
  ) ?{ () -> untyped } -> untyped
241
235
 
242
- # Renders active flash messages as ODT Toasts.
243
236
  def odt_flash_toasts: (
244
237
  ?untyped flash_hash,
245
238
  ?duration: Integer
246
239
  ) -> untyped
247
240
 
248
- # Generates a Turbo Stream action for a toast.
249
241
  def odt_toast_stream: (
250
242
  ?String? message,
251
243
  ?title: String?,
252
244
  ?variant: toast_variant,
245
+ ?type: toast_type,
253
246
  ?target: String,
254
247
  ?action: Symbol | String,
255
248
  ?duration: Integer,
249
+ ?rich_colors: bool,
256
250
  **untyped
257
251
  ) -> untyped
258
252
 
259
- type modal_size = :sm | :md | :lg | :xl | :full | Symbol | String
253
+ type modal_size = :sm | :md | :lg | :xl | :full
260
254
 
261
- # Renders an ODT Modal dialog container.
262
255
  def odt_modal: (
263
256
  ?id: String?,
264
257
  ?title: String?,
@@ -271,7 +264,6 @@ module OdtUi
271
264
  **untyped
272
265
  ) ?{ () -> untyped } -> untyped
273
266
 
274
- # Renders an ODT Modal Header subcomponent.
275
267
  def odt_modal_header: (
276
268
  ?title: String?,
277
269
  ?subtitle: String?,
@@ -279,15 +271,81 @@ module OdtUi
279
271
  **untyped
280
272
  ) ?{ () -> untyped } -> untyped
281
273
 
282
- # Renders an ODT Modal Body subcomponent.
283
274
  def odt_modal_body: (
284
275
  **untyped
285
276
  ) ?{ () -> untyped } -> untyped
286
277
 
287
- # Renders an ODT Modal Footer subcomponent.
288
278
  def odt_modal_footer: (
289
279
  **untyped
290
280
  ) ?{ () -> untyped } -> untyped
281
+
282
+ type dropdown_align = :start | :center | :end
283
+ type dropdown_side = :bottom | :top | :left | :right
284
+ type dropdown_variant = :outlined | :filled | :frosted
285
+ type dropdown_size = :sm | :md | :lg
286
+
287
+ def odt_dropdown_menu: (
288
+ ?open: bool,
289
+ ?close_on_click_outside: bool,
290
+ ?close_on_select: bool,
291
+ ?full_width: bool,
292
+ **untyped
293
+ ) ?{ () -> untyped } -> untyped
294
+
295
+ def odt_dropdown_trigger: (
296
+ ?String? text,
297
+ ?label: String?,
298
+ ?helper: String?,
299
+ ?helper_text: String?,
300
+ ?error: String?,
301
+ ?variant: dropdown_variant,
302
+ ?size: dropdown_size,
303
+ ?radius: radius_size?,
304
+ ?chevron: bool,
305
+ ?placeholder: String?,
306
+ ?left_icon: String?,
307
+ ?right_icon: String?,
308
+ ?full_width: bool,
309
+ ?disabled: bool,
310
+ **untyped
311
+ ) ?{ () -> untyped } -> untyped
312
+
313
+ def odt_dropdown_content: (
314
+ ?align: dropdown_align,
315
+ ?side: dropdown_side,
316
+ ?radius: radius_size?,
317
+ ?shadow: shadow_size?,
318
+ ?full_width: bool,
319
+ **untyped
320
+ ) ?{ () -> untyped } -> untyped
321
+
322
+ def odt_dropdown_item: (
323
+ ?String? text,
324
+ ?href: String?,
325
+ ?left_icon: String?,
326
+ ?right_icon: String?,
327
+ ?shortcut: String?,
328
+ ?destructive: bool,
329
+ ?disabled: bool,
330
+ ?inset: bool,
331
+ ?value: String?,
332
+ **untyped
333
+ ) ?{ () -> untyped } -> untyped
334
+
335
+ def odt_dropdown_label: (
336
+ ?String? text,
337
+ ?inset: bool,
338
+ **untyped
339
+ ) ?{ () -> untyped } -> untyped
340
+
341
+ def odt_dropdown_separator: (
342
+ **untyped
343
+ ) -> untyped
344
+
345
+ def odt_dropdown_shortcut: (
346
+ ?String? text,
347
+ **untyped
348
+ ) ?{ () -> untyped } -> untyped
291
349
  end
292
350
  end
293
351
 
data/lib/odt_ui/engine.rb CHANGED
@@ -7,7 +7,6 @@ module OdtUi
7
7
  initializer "odt_ui.assets" do |app|
8
8
  if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
9
9
  app.config.assets.paths << root.join("app/assets/stylesheets").to_s
10
- app.config.assets.paths << root.join("app/javascript").to_s if root.join("app/javascript").exist?
11
10
  end
12
11
  end
13
12