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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ShadcnPhlexcomponents
4
4
  class Command < Base
5
- class_variants(base: "inline-block max-w-fit")
5
+ class_variants(base: "inline-flex max-w-fit")
6
6
 
7
7
  MODIFIER_KEYS = [
8
8
  :ctrl,
@@ -10,7 +10,16 @@ module ShadcnPhlexcomponents
10
10
  :shift,
11
11
  ]
12
12
 
13
- def initialize(modifier_key: nil, shortcut_key: nil, search_path: nil, open: false, **attributes)
13
+ def initialize(
14
+ open: false,
15
+ modifier_key: nil,
16
+ shortcut_key: nil,
17
+ search_path: nil,
18
+ search_error_text: "Something went wrong, please try again.",
19
+ search_empty_text: "No results found",
20
+ search_placeholder_text: "Search...",
21
+ **attributes
22
+ )
14
23
  if modifier_key && !MODIFIER_KEYS.include?(modifier_key)
15
24
  raise ArgumentError, "Expected one of #{MODIFIER_KEYS} for \"modifier_key\", got #{modifier_key}"
16
25
  end
@@ -19,6 +28,9 @@ module ShadcnPhlexcomponents
19
28
  @modifier_key = modifier_key
20
29
  @shortcut_key = shortcut_key
21
30
  @search_path = search_path
31
+ @search_error_text = search_error_text
32
+ @search_empty_text = search_empty_text
33
+ @search_placeholder_text = search_placeholder_text
22
34
  @aria_id = "command-#{SecureRandom.hex(5)}"
23
35
  super(**attributes)
24
36
  end
@@ -28,7 +40,14 @@ module ShadcnPhlexcomponents
28
40
  end
29
41
 
30
42
  def content(**attributes, &)
31
- CommandContent(aria_id: @aria_id, **attributes, &)
43
+ CommandContent(
44
+ search_error_text: @search_error_text,
45
+ search_empty_text: @search_empty_text,
46
+ search_placeholder_text: @search_placeholder_text,
47
+ aria_id: @aria_id,
48
+ **attributes,
49
+ &
50
+ )
32
51
  end
33
52
 
34
53
  def item(**attributes, &)
@@ -43,24 +62,24 @@ module ShadcnPhlexcomponents
43
62
  CommandGroup(aria_id: @aria_id, **attributes, &)
44
63
  end
45
64
 
46
- def empty(**attributes, &)
47
- CommandEmpty(**attributes, &)
48
- end
49
-
50
65
  def default_attributes
51
66
  {
52
67
  data: {
53
68
  controller: "command",
69
+ command_is_open_value: @open.to_s,
54
70
  modifier_key: @modifier_key,
55
71
  shortcut_key: @shortcut_key,
56
- search_path: @search_path,
57
- command_is_open_value: @open.to_s,
58
- },
72
+ search_path: @search_path
73
+ }
59
74
  }
60
75
  end
61
76
 
62
77
  def view_template(&)
63
- div(**@attributes, &)
78
+ div(**@attributes) do
79
+ overlay("command")
80
+
81
+ yield
82
+ end
64
83
  end
65
84
  end
66
85
 
@@ -91,9 +110,8 @@ module ShadcnPhlexcomponents
91
110
  controls: "#{@aria_id}-content",
92
111
  },
93
112
  data: {
94
- action: "click->command#open",
95
113
  command_target: "trigger",
96
- as_child: @as_child.to_s,
114
+ action: "click->command#open"
97
115
  },
98
116
  }
99
117
  end
@@ -123,18 +141,27 @@ module ShadcnPhlexcomponents
123
141
  bg-background bg-clip-padding dark:bg-neutral-900 dark:ring-neutral-800 data-[state=closed]:animate-out#{" "}
124
142
  data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0
125
143
  data-[state=open]:zoom-in-95 duration-200 fixed gap-4 grid left-[50%] max-w-[calc(100%-2rem)] p-2 pb-11 ring-4 ring-neutral-200/80
126
- rounded-xl shadow-2xl sm:max-w-lg top-[50%] translate-x-[-50%] translate-y-[-50%] w-full z-50 pointer-events-auto
144
+ rounded-xl shadow-2xl sm:max-w-lg top-[50%] translate-x-[-50%] translate-y-[-50%] w-full z-50 pointer-events-auto outline-none
127
145
  HEREDOC
128
146
  )
129
147
 
130
- def initialize(search_placeholder: "Search...", aria_id: nil, **attributes)
131
- @search_placeholder = search_placeholder
148
+ def initialize(
149
+ search_error_text: nil,
150
+ search_empty_text: nil,
151
+ search_placeholder_text: nil,
152
+ aria_id: nil,
153
+ **attributes
154
+ )
155
+ @search_error_text = search_error_text
156
+ @search_empty_text = search_empty_text
157
+ @search_placeholder_text = search_placeholder_text
132
158
  @aria_id = aria_id
133
159
  super(**attributes)
134
160
  end
135
161
 
136
162
  def default_attributes
137
163
  {
164
+ style: { display: "none" },
138
165
  id: "#{@aria_id}-content",
139
166
  tabindex: -1,
140
167
  role: "dialog",
@@ -143,26 +170,29 @@ module ShadcnPhlexcomponents
143
170
  labelledby: "#{@aria_id}-title",
144
171
  },
145
172
  data: {
146
- command_target: "content",
147
173
  state: "closed",
174
+ command_target: "content",
148
175
  action: <<~HEREDOC,
149
- command:click:outside->command#close
176
+ command:click:outside->command#clickOutside
150
177
  keydown.up->command#highlightItem:prevent
151
178
  keydown.down->command#highlightItem:prevent
152
179
  keydown.enter->command#select
153
180
  HEREDOC
154
-
155
181
  },
156
182
  }
157
183
  end
158
184
 
159
185
  def view_template(&)
160
- @class = @attributes.delete(:class)
186
+ div(**@attributes) do
187
+ template do
188
+ CommandGroup do
189
+ CommandLabel { "" }
190
+ end
191
+ end
161
192
 
162
- div(class: "#{@class} hidden", **@attributes) do
163
193
  div(class: "text-popover-foreground flex h-full w-full flex-col overflow-hidden bg-transparent") do
164
194
  div(class: "sr-only") do
165
- h2(id: "#{@aria_id}-title") { @search_placeholder }
195
+ h2(id: "#{@aria_id}-title") { @search_placeholder_text }
166
196
  p(id: "#{@aria_id}-description") { "Search for a command to run..." }
167
197
  end
168
198
 
@@ -170,7 +200,7 @@ module ShadcnPhlexcomponents
170
200
  class: "sr-only",
171
201
  id: "#{@aria_id}-search-label",
172
202
  for: "#{@aria_id}-search",
173
- ) { @search_placeholder }
203
+ ) { @search_placeholder_text }
174
204
 
175
205
  div(class: "flex h-9 items-center gap-2 border px-3 bg-input/50 border-input rounded-md") do
176
206
  icon("search", class: "size-4 shrink-0 opacity-50")
@@ -179,7 +209,7 @@ module ShadcnPhlexcomponents
179
209
  class: "placeholder:text-muted-foreground flex w-full rounded-md bg-transparent py-3 text-sm
180
210
  outline-hidden disabled:cursor-not-allowed disabled:opacity-50 h-9",
181
211
  id: "#{@aria_id}-search",
182
- placeholder: @search_placeholder,
212
+ placeholder: @search_placeholder_text,
183
213
  type: :text,
184
214
  autocomplete: "off",
185
215
  autocorrect: "off",
@@ -193,13 +223,21 @@ module ShadcnPhlexcomponents
193
223
  },
194
224
  data: {
195
225
  command_target: "searchInput",
196
- action: "input->command#search",
226
+ action: "keydown->command#inputKeydown input->command#search",
197
227
  },
198
228
  )
199
229
  end
200
230
 
201
- div(class: "p-1 max-h-80 min-h-80 overflow-y-auto", id: "#{@aria_id}-list", data: { command_target: "list" }) do
202
- div(data: { command_target: "results" }, &)
231
+ div(class: "mt-3 p-1 max-h-80 min-h-80 overflow-y-auto", data: { command_target: "listContainer"}) do
232
+ CommandText(target: "empty") { @search_empty_text }
233
+ CommandText(target: "error") { @search_error_text }
234
+ CommandText(target: "loading") do
235
+ div(class: "flex justify-center", aria: { label: "Loading" }) do
236
+ icon("loader-circle", class: "animate-spin")
237
+ end
238
+ end
239
+
240
+ div(id: "#{@aria_id}-list", data: { command_target: "list" }, &)
203
241
  end
204
242
 
205
243
  CommandFooter()
@@ -251,30 +289,21 @@ module ShadcnPhlexcomponents
251
289
  end
252
290
 
253
291
  class CommandLabel < Base
254
- class_variants(base: "text-muted-foreground text-xs p-3 pb-1 text-xs font-medium")
292
+ class_variants(base: "text-muted-foreground text-xs px-3 pb-1 text-xs font-medium")
255
293
 
256
- def initialize(aria_id: nil, **attributes)
257
- @aria_id = aria_id
294
+ def initialize( **attributes)
258
295
  super(**attributes)
259
296
  end
260
297
 
261
298
  def view_template(&)
262
299
  div(**@attributes, &)
263
300
  end
264
-
265
- def default_attributes
266
- {
267
- data: {
268
- command_target: "label",
269
- },
270
- }
271
- end
272
301
  end
273
302
 
274
303
  class CommandGroup < Base
275
- class_variants(base: "scroll-mt-16")
304
+ class_variants(base: "scroll-mt-16 first:pt-0 pt-3")
276
305
 
277
- def initialize(aria_id:, **attributes)
306
+ def initialize(aria_id: nil, **attributes)
278
307
  @aria_id = aria_id
279
308
  super(**attributes)
280
309
  end
@@ -296,22 +325,23 @@ module ShadcnPhlexcomponents
296
325
  end
297
326
  end
298
327
 
299
- class CommandEmpty < Base
328
+ class CommandText < Base
300
329
  class_variants(base: "py-6 text-center text-sm hidden")
330
+
331
+ def initialize(target:, **attributes)
332
+ @target = target
333
+ super(**attributes)
334
+ end
301
335
 
302
336
  def default_attributes
303
337
  {
304
338
  role: "presentation",
305
- data: { command_target: "empty" },
339
+ data: { command_target: @target },
306
340
  }
307
341
  end
308
342
 
309
343
  def view_template(&)
310
- if block_given?
311
- div(**@attributes, &)
312
- else
313
- div(**@attributes) { "No results found" }
314
- end
344
+ div(**@attributes, &)
315
345
  end
316
346
  end
317
347
 
@@ -4,74 +4,6 @@ module ShadcnPhlexcomponents
4
4
  class DatePicker < Base
5
5
  class_variants(base: "w-full")
6
6
 
7
- class << self
8
- button = Button.new
9
-
10
- {
11
- input_container: <<~HEREDOC,
12
- focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
13
- data-[focus=true]:border-ring data-[focus=true]:ring-ring/50 data-[focus=true]:ring-[3px]
14
- data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 flex shadow-xs transition-[color,box-shadow]
15
- rounded-md border bg-transparent dark:bg-input/30 border-input outline-none h-9 flex items-center
16
- aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive
17
- HEREDOC
18
- input: <<~HEREDOC,
19
- md:text-sm placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground
20
- flex h-9 w-full min-w-0 text-base outline-none px-3 py-1#{" "}
21
- HEREDOC
22
- calendar: {
23
- calendar: "relative flex flex-col outline-none",
24
- controls: "absolute z-20 -left-1 -right-1 -top-1.5 flex justify-between items-center pt-1.5 px-1 pointer-events-none box-content",
25
- grid: "grid gap-4 grid-cols-1 md:grid-cols-2",
26
- column: "flex flex-col",
27
- header: "relative flex items-center mb-4",
28
- headerContent: "grid grid-flow-col gap-x-1 auto-cols-max items-center justify-center px-4 whitespace-pre-wrap grow",
29
- month: button.class_variants(variant: :outline, size: :sm, class: "text-xs h-7 bg-transparent"),
30
- year: button.class_variants(variant: :outline, size: :sm, class: "text-xs h-7 bg-transparent"),
31
- arrowPrev: button.class_variants(variant: :outline, size: :icon, class: "pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100"),
32
- arrowNext: button.class_variants(variant: :outline, size: :icon, class: "pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100"),
33
- wrapper: "flex items-center content-center h-full",
34
- content: "flex flex-col grow h-full",
35
- months: "grid gap-2 grid-cols-4 items-center grow",
36
- monthsMonth: button.class_variants(variant: :ghost, class: "aria-[selected=true]:text-primary-foreground aria-[selected=true]:bg-primary aria-[selected=true]:hover:text-primary-foreground aria-[selected=true]:hover:bg-primary"),
37
- years: "grid gap-2 grid-cols-5 items-center grow",
38
- yearsYear: button.class_variants(variant: :ghost, class: "aria-[selected=true]:text-primary-foreground aria-[selected=true]:bg-primary aria-[selected=true]:hover:text-primary-foreground aria-[selected=true]:hover:bg-primary"),
39
- week: "grid mb-2 grid-cols-[repeat(7,_1fr)] justify-items-center items-center text-center",
40
- weekDay: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
41
- weekNumbers: "vc-week-numbers",
42
- weekNumbersTitle: "vc-week-numbers__title",
43
- weekNumbersContent: "vc-week-numbers__content",
44
- weekNumber: "vc-week-number",
45
- dates: "grid gap-y-2 grid-cols-[repeat(7,_1fr)] justify-items-center items-center",
46
- date: <<~HEREDOC,
47
- vc-date data-[vc-date-selected]:[&_button]:bg-primary#{" "}
48
- data-[vc-date-selected]:[&_button]:text-primary-foreground#{" "}
49
- data-[vc-date-selected]:[&_button]:hover:bg-primary#{" "}
50
- data-[vc-date-selected]:[&_button]:hover:text-primary-foreground#{" "}
51
- data-[vc-date-selected]:[&_button]:focus:bg-primary#{" "}
52
- data-[vc-date-selected]:[&_button]:focus:text-primary-foreground
53
- data-[vc-date-today]:[&_button]:bg-accent
54
- data-[vc-date-today]:[&_button]:text-accent-foreground
55
- data-[vc-date-month=prev]:[&_button]:text-muted-foreground
56
- data-[vc-date-month=next]:[&_button]:text-muted-foreground
57
- data-[vc-date-selected='middle']:data-[vc-date-selected]:[&_button]:bg-accent
58
- data-[vc-date-selected='middle']:data-[vc-date-selected]:[&_button]:text-accent-foreground
59
- data-[vc-date-hover]:[&_button]:bg-accent data-[vc-date-hover]:[&_button]:text-accent-foreground
60
- HEREDOC
61
- dateBtn: button.class_variants(variant: :ghost, class: "size-8 p-0 font-normal aria-[disabled]:text-muted-foreground aria-[disabled]:opacity-50 aria-[disabled]:pointer-events-none"),
62
- datePopup: "vc-date__popup",
63
- dateRangeTooltip: "vc-date-range-tooltip",
64
- time: "vc-time",
65
- timeContent: "vc-time__content",
66
- timeHour: "vc-time__hour",
67
- timeMinute: "vc-time__minute",
68
- timeKeeping: "vc-time__keeping",
69
- timeRanges: "vc-time__ranges",
70
- timeRange: "vc-time__range",
71
- },
72
- }
73
- end
74
-
75
7
  def initialize(
76
8
  name: nil,
77
9
  id: nil,
@@ -86,6 +18,15 @@ module ShadcnPhlexcomponents
86
18
  )
87
19
  @name = name
88
20
  @id = id
21
+
22
+ if value
23
+ value = if value.is_a?(String)
24
+ DateTime.parse(value) rescue nil
25
+ else
26
+ value
27
+ end
28
+ end
29
+
89
30
  @value = value&.utc&.iso8601
90
31
  @format = format
91
32
  @select_only = select_only
@@ -93,8 +34,7 @@ module ShadcnPhlexcomponents
93
34
  @disabled = disabled
94
35
  @mask = mask
95
36
  @aria_id = "date-picker-#{SecureRandom.hex(5)}"
96
- @date_picker_styles = self.class.date_picker_styles
97
- @options = options.merge(styles: @date_picker_styles[:calendar])
37
+ @options = options
98
38
  super(**attributes)
99
39
  end
100
40
 
@@ -112,6 +52,8 @@ module ShadcnPhlexcomponents
112
52
 
113
53
  def view_template(&)
114
54
  div(**@attributes) do
55
+ overlay("date-picker")
56
+
115
57
  input(
116
58
  type: :hidden,
117
59
  name: @name,
@@ -130,12 +72,19 @@ module ShadcnPhlexcomponents
130
72
  placeholder: @placeholder,
131
73
  )
132
74
  else
133
- div(class: @date_picker_styles[:input_container], data: { date_picker_target: "inputContainer", disabled: @disabled }) do
75
+ div(class: <<~HEREDOC,
76
+ focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
77
+ data-[focus=true]:border-ring data-[focus=true]:ring-ring/50 data-[focus=true]:ring-[3px]
78
+ data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 flex shadow-xs transition-[color,box-shadow]
79
+ rounded-md border bg-transparent dark:bg-input/30 border-input outline-none h-9 flex items-center
80
+ aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive
81
+ HEREDOC
82
+ data: { date_picker_target: "inputContainer", disabled: @disabled }) do
134
83
  input(
135
84
  id: @id,
136
85
  placeholder: @placeholder || @format,
137
86
  type: :text,
138
- class: @date_picker_styles[:input],
87
+ class: "md:text-sm placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 text-base outline-none px-3 py-1",
139
88
  disabled: @disabled,
140
89
  data: {
141
90
  date_picker_target: "input",
@@ -223,13 +172,7 @@ module ShadcnPhlexcomponents
223
172
  end
224
173
 
225
174
  def class_variants(**args)
226
- PopoverContent.new.class_variants(
227
- class: <<~HEREDOC,
228
- fixed left-1/2 top-1/2 shadow-lg -translate-x-1/2 -translate-y-1/2 pointer-events-auto w-max
229
- md:relative md:left-[unset] md:top-[unset] md:shadow-md md:translate-x-[unset] md:translate-y-[unset] md:pointer-events-[unset]
230
- #{args[:class]}
231
- HEREDOC
232
- )
175
+ PopoverContent.new.class_variants(class: "w-fit #{args[:class]}")
233
176
  end
234
177
 
235
178
  def default_attributes
@@ -252,7 +195,8 @@ module ShadcnPhlexcomponents
252
195
 
253
196
  def view_template(&)
254
197
  div(
255
- class: "hidden fixed top-0 left-0 w-max z-50",
198
+ style: { display: "none" },
199
+ class: "fixed z-50 top-1/4 left-1/2 -translate-x-1/2 pointer-events-auto md:top-auto md:left-auto md:translate-none md:pointer-events-[unset]",
256
200
  data: { "#{stimulus_controller_name}-target" => "contentContainer" },
257
201
  ) do
258
202
  div(**@attributes) do
@@ -261,4 +205,4 @@ module ShadcnPhlexcomponents
261
205
  end
262
206
  end
263
207
  end
264
- end
208
+ end
@@ -24,6 +24,16 @@ module ShadcnPhlexcomponents
24
24
  raise ArgumentError, "Expected an array for \"value\", got #{value.class}"
25
25
  end
26
26
 
27
+ if value
28
+ value = value.map do |v|
29
+ if v.is_a?(String)
30
+ DateTime.parse(v) rescue nil
31
+ else
32
+ v
33
+ end
34
+ end
35
+ end
36
+
27
37
  @name = name ? name[0] : nil
28
38
  @end_name = name ? name[1] : nil
29
39
  @value = (value ? value[0] : nil)&.utc&.iso8601
@@ -35,8 +45,7 @@ module ShadcnPhlexcomponents
35
45
  @disabled = disabled
36
46
  @mask = mask
37
47
  @aria_id = "date-range-picker-#{SecureRandom.hex(5)}"
38
- @date_picker_styles = DatePicker.date_picker_styles
39
- @options = options.merge(styles: @date_picker_styles[:calendar])
48
+ @options = options
40
49
  super(**attributes)
41
50
  end
42
51
 
@@ -55,6 +64,8 @@ module ShadcnPhlexcomponents
55
64
 
56
65
  def view_template(&)
57
66
  div(**@attributes) do
67
+ overlay('date-range-picker')
68
+
58
69
  input(
59
70
  type: :hidden,
60
71
  name: @name,
@@ -81,14 +92,20 @@ module ShadcnPhlexcomponents
81
92
  )
82
93
  else
83
94
  div(
84
- class: @date_picker_styles[:input_container],
95
+ class: <<~HEREDOC,
96
+ focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]
97
+ data-[focus=true]:border-ring data-[focus=true]:ring-ring/50 data-[focus=true]:ring-[3px]
98
+ data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 flex shadow-xs transition-[color,box-shadow]
99
+ rounded-md border bg-transparent dark:bg-input/30 border-input outline-none h-9 flex items-center
100
+ aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive
101
+ HEREDOC
85
102
  data: { date_range_picker_target: "inputContainer", disabled: @disabled },
86
103
  ) do
87
104
  input(
88
105
  id: @id,
89
106
  placeholder: @placeholder || "#{@format} - #{@format}",
90
107
  type: :text,
91
- class: @date_picker_styles[:input],
108
+ class: "md:text-sm placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 text-base outline-none px-3 py-1",
92
109
  disabled: @disabled,
93
110
  data: {
94
111
  date_range_picker_target: "input",
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ShadcnPhlexcomponents
4
4
  class Dialog < Base
5
- class_variants(base: "inline-block max-w-fit")
5
+ class_variants(base: "inline-flex max-w-fit")
6
6
 
7
7
  def initialize(open: false, **attributes)
8
8
  @open = open
@@ -42,13 +42,17 @@ module ShadcnPhlexcomponents
42
42
  {
43
43
  data: {
44
44
  controller: "dialog",
45
- dialog_is_open_value: @open.to_s,
46
- },
45
+ dialog_is_open_value: @open.to_s
46
+ }
47
47
  }
48
48
  end
49
49
 
50
50
  def view_template(&)
51
- div(**@attributes, &)
51
+ div(**@attributes) do
52
+ overlay("dialog")
53
+
54
+ yield
55
+ end
52
56
  end
53
57
  end
54
58
 
@@ -67,10 +71,10 @@ module ShadcnPhlexcomponents
67
71
  expanded: "false",
68
72
  controls: "#{@aria_id}-content",
69
73
  },
70
- data: {
71
- action: "click->dialog#open",
72
- dialog_target: "trigger",
74
+ data: {
73
75
  as_child: @as_child.to_s,
76
+ dialog_target: "trigger",
77
+ action: "click->dialog#open"
74
78
  },
75
79
  }
76
80
  end
@@ -98,7 +102,7 @@ module ShadcnPhlexcomponents
98
102
  data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95
99
103
  data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)]
100
104
  translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg
101
- pointer-events-auto
105
+ pointer-events-auto outline-none
102
106
  HEREDOC
103
107
  )
104
108
 
@@ -117,16 +121,14 @@ module ShadcnPhlexcomponents
117
121
  labelledby: "#{@aria_id}-title",
118
122
  },
119
123
  data: {
120
- dialog_target: "content",
121
124
  state: "closed",
122
- action: "dialog:click:outside->dialog#close",
125
+ dialog_target: "content"
123
126
  },
124
127
  }
125
128
  end
126
129
 
127
130
  def view_template(&)
128
- @class = @attributes.delete(:class)
129
- div(class: "#{@class} hidden", **@attributes) do
131
+ div(style: { display: "none" }, **@attributes) do
130
132
  yield
131
133
 
132
134
  button(
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ShadcnPhlexcomponents
4
4
  class DropdownMenu < Base
5
- class_variants(base: "inline-block max-w-fit")
5
+ class_variants(base: "inline-flex max-w-fit")
6
6
 
7
7
  def initialize(open: false, **attributes)
8
8
  @aria_id = "dropdown-menu-#{SecureRandom.hex(5)}"
@@ -93,9 +93,9 @@ module ShadcnPhlexcomponents
93
93
  dropdown_menu_target: "trigger",
94
94
  action: <<~HEREDOC,
95
95
  click->dropdown-menu#toggle
96
- keydown.space->dropdown-menu#open
97
- keydown.enter->dropdown-menu#open
98
96
  keydown.down->dropdown-menu#open:prevent
97
+ keydown.space->dropdown-menu#open:prevent
98
+ keydown.enter->dropdown-menu#open:prevent
99
99
  HEREDOC
100
100
  },
101
101
  }
@@ -123,7 +123,8 @@ module ShadcnPhlexcomponents
123
123
 
124
124
  def view_template(&)
125
125
  div(
126
- class: "hidden fixed top-0 left-0 w-max z-50",
126
+ style: { display: "none" },
127
+ class: "fixed top-0 left-0 w-max z-50",
127
128
  data: { dropdown_menu_target: "contentContainer" },
128
129
  ) do
129
130
  div(**@attributes, &)
@@ -125,7 +125,8 @@ module ShadcnPhlexcomponents
125
125
 
126
126
  def view_template(&)
127
127
  div(
128
- class: "hidden fixed top-0 left-0 w-max z-50",
128
+ style: { display: "none" },
129
+ class: "fixed top-0 left-0 w-max z-50",
129
130
  data: { dropdown_menu_sub_target: "contentContainer" },
130
131
  ) do
131
132
  div(**@attributes, &)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class FormCombobox < Base
5
+ include FormHelpers
6
+
7
+ def initialize(
8
+ method = nil,
9
+ model: false,
10
+ object_name: nil,
11
+ collection: [],
12
+ value_method: nil,
13
+ text_method: nil,
14
+ value: nil,
15
+ name: nil,
16
+ id: nil,
17
+ label: nil,
18
+ error: nil,
19
+ hint: nil,
20
+ disabled_items: nil,
21
+ **attributes
22
+ )
23
+ @method = method
24
+ @model = model
25
+ @object_name = object_name
26
+
27
+ @collection = if collection.first&.is_a?(Hash)
28
+ convert_collection_hash_to_struct(collection, value_method: value_method, text_method: text_method)
29
+ else
30
+ collection
31
+ end
32
+
33
+ @value_method = value_method
34
+ @text_method = text_method
35
+ @value = default_value(value, method)
36
+ @name = name
37
+ @id = id
38
+ @label = label
39
+ @error = default_error(error, method)
40
+ @hint = hint
41
+ @disabled_items = disabled_items
42
+ @aria_id = "form-field-#{SecureRandom.hex(5)}"
43
+ super(**attributes)
44
+ end
45
+
46
+ def view_template(&)
47
+ vanish(&)
48
+
49
+ @id ||= field_id(@object_name, @method)
50
+ @name ||= field_name(@object_name, @method)
51
+
52
+ div(class: "space-y-2", data: label_and_hint_container_attributes) do
53
+ render_label(&)
54
+
55
+ Combobox(id: @id, name: @name, value: @value, aria: aria_attributes, **@attributes) do |c|
56
+ c.items(@collection, value_method: @value_method, text_method: @text_method, disabled_items: @disabled_items)
57
+ end
58
+
59
+ render_hint(&)
60
+ render_error
61
+ end
62
+ end
63
+ end
64
+ end