fluxbit_view_components 0.1.0

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +86 -0
  4. data/app/components/fluxbit/alert_component.rb +126 -0
  5. data/app/components/fluxbit/avatar_component.rb +113 -0
  6. data/app/components/fluxbit/avatar_group_component.rb +23 -0
  7. data/app/components/fluxbit/badge_component.rb +79 -0
  8. data/app/components/fluxbit/button_component.rb +97 -0
  9. data/app/components/fluxbit/button_group_component.rb +43 -0
  10. data/app/components/fluxbit/card_component.rb +135 -0
  11. data/app/components/fluxbit/component.rb +86 -0
  12. data/app/components/fluxbit/flex_component.rb +93 -0
  13. data/app/components/fluxbit/form/checkbox_input_component.rb +61 -0
  14. data/app/components/fluxbit/form/component.rb +71 -0
  15. data/app/components/fluxbit/form/datepicker_component.rb +7 -0
  16. data/app/components/fluxbit/form/form_builder_component.rb +117 -0
  17. data/app/components/fluxbit/form/helper_text_component.rb +29 -0
  18. data/app/components/fluxbit/form/label_component.rb +65 -0
  19. data/app/components/fluxbit/form/radio_input_component.rb +21 -0
  20. data/app/components/fluxbit/form/range_input_component.rb +51 -0
  21. data/app/components/fluxbit/form/select_free_input_component.rb +77 -0
  22. data/app/components/fluxbit/form/select_input_component.rb +21 -0
  23. data/app/components/fluxbit/form/spacer_input_component.rb +12 -0
  24. data/app/components/fluxbit/form/text_input_component.rb +225 -0
  25. data/app/components/fluxbit/form/textarea_input_component.rb +57 -0
  26. data/app/components/fluxbit/form/toggle_input_component.rb +166 -0
  27. data/app/components/fluxbit/form/upload_image_input_component.html.erb +48 -0
  28. data/app/components/fluxbit/form/upload_image_input_component.rb +66 -0
  29. data/app/components/fluxbit/form/upload_input_component.html.erb +12 -0
  30. data/app/components/fluxbit/form/upload_input_component.rb +47 -0
  31. data/app/components/fluxbit/gravatar_component.rb +99 -0
  32. data/app/components/fluxbit/heading_component.rb +47 -0
  33. data/app/components/fluxbit/modal_component.rb +141 -0
  34. data/app/components/fluxbit/popover_component.rb +71 -0
  35. data/app/components/fluxbit/tab_component.rb +142 -0
  36. data/app/components/fluxbit/text_component.rb +36 -0
  37. data/app/components/fluxbit/tooltip_component.rb +38 -0
  38. data/app/helpers/fluxbit/classes_helper.rb +21 -0
  39. data/app/helpers/fluxbit/components_helper.rb +75 -0
  40. data/config/deploy.yml +37 -0
  41. data/config/locales/en.yml +6 -0
  42. data/lib/fluxbit/config/alert_component.rb +59 -0
  43. data/lib/fluxbit/config/avatar_component.rb +79 -0
  44. data/lib/fluxbit/config/badge_component.rb +77 -0
  45. data/lib/fluxbit/config/button_component.rb +86 -0
  46. data/lib/fluxbit/config/card_component.rb +32 -0
  47. data/lib/fluxbit/config/flex_component.rb +63 -0
  48. data/lib/fluxbit/config/form/helper_text_component.rb +20 -0
  49. data/lib/fluxbit/config/gravatar_component.rb +19 -0
  50. data/lib/fluxbit/config/heading_component.rb +39 -0
  51. data/lib/fluxbit/config/modal_component.rb +71 -0
  52. data/lib/fluxbit/config/paragraph_component.rb +11 -0
  53. data/lib/fluxbit/config/popover_component.rb +33 -0
  54. data/lib/fluxbit/config/tab_component.rb +131 -0
  55. data/lib/fluxbit/config/text_component.rb +110 -0
  56. data/lib/fluxbit/config/tooltip_component.rb +11 -0
  57. data/lib/fluxbit/view_components/codemods/v3_slot_setters.rb +222 -0
  58. data/lib/fluxbit/view_components/engine.rb +36 -0
  59. data/lib/fluxbit/view_components/version.rb +7 -0
  60. data/lib/fluxbit/view_components.rb +30 -0
  61. data/lib/fluxbit_view_components.rb +3 -0
  62. data/lib/install/install.rb +64 -0
  63. data/lib/tasks/fluxbit_view_components_tasks.rake +22 -0
  64. metadata +238 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::HeadingComponent
4
+ mattr_accessor :size, default: 1
5
+ mattr_accessor :spacing, default: :tight
6
+ mattr_accessor :line_height, default: :none
7
+
8
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
9
+ mattr_accessor :styles do
10
+ {
11
+ base: "mb-4 text-gray-900 dark:text-white",
12
+ sizes: {
13
+ h1: "text-5xl font-extrabold md:text-6xl lg:text-7xl",
14
+ h2: "text-4xl font-bold md:text-5xl lg:text-6xl",
15
+ h3: "text-3xl font-bold md:text-4xl lg:text-5xl",
16
+ h4: "text-2xl font-bold md:text-3xl lg:text-4xl",
17
+ h5: "text-xl font-bold md:text-2xl lg:text-3xl",
18
+ h6: "text-lg font-bold md:text-xl lg:text-2xl"
19
+ },
20
+ spacings: {
21
+ tighter: "tracking-tighter",
22
+ tight: "tracking-tight",
23
+ normal: "tracking-normal",
24
+ wide: "tracking-wide",
25
+ wider: "tracking-wider",
26
+ widest: "tracking-widest"
27
+ },
28
+ line_heights: {
29
+ none: "leading-none",
30
+ tight: "leading-tight",
31
+ snug: "leading-snug",
32
+ normal: "leading-normal",
33
+ relaxed: "leading-relaxed",
34
+ loose: "leading-loose"
35
+ }
36
+ }
37
+ end
38
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
39
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::ModalComponent
4
+ mattr_accessor :opened, default: false
5
+ mattr_accessor :close_button, default: true
6
+ mattr_accessor :flat, default: false
7
+ mattr_accessor :size, default: 1
8
+ mattr_accessor :only_css, default: false
9
+ mattr_accessor :static, default: false
10
+ mattr_accessor :placement, default: nil
11
+
12
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
13
+ mattr_accessor :styles do
14
+ {
15
+ root: {
16
+ base: "fixed inset-x-0 top-0 z-50 h-screen overflow-y-auto overflow-x-hidden md:inset-0 md:h-full flex",
17
+ backdrop: "bg-gray-900/50 dark:bg-gray-900/80",
18
+ show: {
19
+ on: "",
20
+ off: "hidden"
21
+ },
22
+ size: [
23
+ "max-w-sm",
24
+ "max-w-md",
25
+ "max-w-lg",
26
+ "max-w-xl",
27
+ "max-w-2xl",
28
+ "max-w-3xl",
29
+ "max-w-4xl",
30
+ "max-w-5xl",
31
+ "max-w-6xl",
32
+ "max-w-7xl"
33
+ ],
34
+ placements: {
35
+ "top-left": "items-start justify-start",
36
+ "top-center": "items-start justify-center",
37
+ "top-right": "items-start justify-end",
38
+ "center-left": "items-center justify-start",
39
+ "center": "items-center justify-center",
40
+ "center-right": "items-center justify-end",
41
+ "bottom-right": "items-end justify-end",
42
+ "bottom-center": "items-end justify-center",
43
+ "bottom-left": "items-end justify-start"
44
+ }
45
+ },
46
+ content: {
47
+ base: "relative h-full w-full p-4 md:h-auto",
48
+ inner: "relative flex max-h-[90dvh] flex-col rounded-lg bg-white shadow-sm dark:bg-gray-700"
49
+ },
50
+ body: {
51
+ base: "flex-1 overflow-auto p-6",
52
+ flat: "pt-0",
53
+ no_title: "-mt-4"
54
+ },
55
+ header: {
56
+ base: "flex items-start justify-between rounded-t border-b p-5 dark:border-gray-600",
57
+ flat: "border-b-0 p-2",
58
+ title: "text-xl font-medium text-gray-900 dark:text-white",
59
+ close: {
60
+ base: "close-button ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white",
61
+ icon: "h-5 w-5"
62
+ }
63
+ },
64
+ footer: {
65
+ base: "flex items-center space-x-2 rounded-b border-t border-gray-200 p-6 dark:border-gray-600",
66
+ flat: "border-t-0"
67
+ }
68
+ }
69
+ end
70
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
71
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::ParagraphComponent
4
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
5
+ mattr_accessor :styles do
6
+ {
7
+ base: "mb-3 text-gray-500 dark:text-gray-400"
8
+ }
9
+ end
10
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::PopoverComponent
4
+ mattr_accessor :has_arrow, default: true
5
+ mattr_accessor :image_position, default: :right
6
+ mattr_accessor :image_props, default: {}
7
+ mattr_accessor :size, default: 2
8
+
9
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
10
+ mattr_accessor :styles do
11
+ {
12
+ base: "absolute z-10 invisible inline-block text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-xs opacity-0 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800",
13
+ size: [
14
+ "w-24",
15
+ "w-32",
16
+ "w-48",
17
+ "w-64",
18
+ "w-96"
19
+ ],
20
+ title: {
21
+ div: "px-3 py-2 bg-gray-100 border-b border-gray-200 rounded-t-lg dark:border-gray-600 dark:bg-gray-700",
22
+ h3: "font-semibold text-gray-900 dark:text-white"
23
+ },
24
+ content: "px-3 py-2",
25
+ image_base: "grid grid-cols-5",
26
+ image_content: {
27
+ text: "col-span-3 p-3 space-y-2",
28
+ image: "h-full col-span-2"
29
+ }
30
+ }
31
+ end
32
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
33
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::TabComponent
4
+ mattr_accessor :variant, default: :default
5
+ mattr_accessor :color, default: :blue
6
+ mattr_accessor :vertical, default: false
7
+ mattr_accessor :tab_panel, default: :default
8
+
9
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
10
+ mattr_accessor :styles do
11
+ {
12
+ div: {
13
+ horizontal: "",
14
+ vertical: "md:flex"
15
+ },
16
+ tab_list: {
17
+ ul: {
18
+ horizontal: "flex text-center",
19
+ vertical: "flex-column space-y space-y-4 text-sm font-medium text-gray-500 dark:text-gray-400 md:me-4 mb-4 md:mb-0"
20
+ },
21
+ li: "",
22
+ variant: {
23
+ default: "flex-wrap text-sm font-medium text-gray-500 border-b border-gray-200 dark:border-gray-700 dark:text-gray-400",
24
+ underline: "-mb-px flex-wrap border-b border-gray-200 dark:border-gray-700",
25
+ pills: "flex-wrap space-x-2 text-sm font-medium text-gray-500 dark:text-gray-400",
26
+ full_width: "grid w-full grid-flow-col divide-x divide-gray-200 rounded-none text-sm font-medium shadow dark:divide-gray-700 dark:text-gray-400"
27
+ },
28
+ tab_item: {
29
+ base: "inline-flex w-full",
30
+ variant: {
31
+ default: {
32
+ base: "inline-block p-4 rounded-t-lg",
33
+ active: {
34
+ blue: "text-blue-600 bg-gray-100 active dark:bg-gray-800 dark:text-blue-500",
35
+ cyan: "text-cyan-600 bg-gray-100 active dark:bg-gray-800 dark:text-cyan-500",
36
+ green: "text-green-600 bg-gray-100 active dark:bg-gray-800 dark:text-green-500",
37
+ indigo: "text-indigo-600 bg-gray-100 active dark:bg-gray-800 dark:text-indigo-500",
38
+ pink: "text-pink-600 bg-gray-100 active dark:bg-gray-800 dark:text-pink-500",
39
+ purple: "text-purple-600 bg-gray-100 active dark:bg-gray-800 dark:text-purple-500",
40
+ red: "text-red-600 bg-gray-100 active dark:bg-gray-800 dark:text-red-500",
41
+ yellow: "text-yellow-600 bg-gray-100 active dark:bg-gray-800 dark:text-yellow-500",
42
+ gray: "text-gray-600 bg-gray-100 active dark:bg-gray-700 dark:text-white"
43
+ },
44
+ inactive: "hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300",
45
+ disabled: "text-gray-400 cursor-not-allowed dark:text-gray-500"
46
+ },
47
+ underline: {
48
+ base: "inline-block p-4 rounded-t-lg border-b-2 ",
49
+ active: {
50
+ blue: "active text-blue-600 border-blue-600 dark:text-blue-500 dark:border-blue-500",
51
+ cyan: "active text-cyan-600 border-cyan-600 dark:text-cyan-500 dark:border-cyan-500",
52
+ green: "active text-green-600 border-green-600 dark:text-green-500 dark:border-green-500",
53
+ indigo: "active text-indigo-600 border-indigo-600 dark:text-indigo-500 dark:border-indigo-500",
54
+ pink: "active text-pink-600 border-pink-600 dark:text-pink-500 dark:border-pink-500",
55
+ purple: "active text-purple-600 border-purple-600 dark:text-purple-500 dark:border-purple-500",
56
+ red: "active text-red-600 border-red-600 dark:text-red-500 dark:border-red-500",
57
+ yellow: "active text-yellow-600 border-yellow-600 dark:text-yellow-500 dark:border-yellow-500",
58
+ gray: "active text-gray-600 border-gray-600 dark:text-gray-500 dark:border-gray-500"
59
+ },
60
+ inactive: "border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300",
61
+ disabled: "text-gray-400 border-transparent cursor-not-allowed dark:text-gray-500"
62
+ },
63
+ pills: {
64
+ base: "inline-block px-4 py-3 rounded-lg",
65
+ active: {
66
+ blue: "active text-white bg-blue-600",
67
+ cyan: "active text-white bg-cyan-600",
68
+ green: "active text-white bg-green-600",
69
+ indigo: "active text-white bg-indigo-600",
70
+ pink: "active text-white bg-pink-600",
71
+ purple: "active text-white bg-purple-600",
72
+ red: "active text-white bg-red-600",
73
+ yellow: "active text-white bg-yellow-600",
74
+ gray: "active text-white bg-gray-600"
75
+ },
76
+ inactive: "hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white",
77
+ disabled: "text-gray-400 dark:text-gray-500 cursor-not-allowed"
78
+ },
79
+ full_width: {
80
+ base: "inline-block w-full p-4 border-r border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-blue-300 focus:outline-none",
81
+ active: {
82
+ blue: "text-blue-600 bg-gray-100 active dark:text-blue-500 dark:bg-gray-800",
83
+ cyan: "text-cyan-600 bg-gray-100 active dark:text-cyan-500 dark:bg-gray-800",
84
+ green: "text-green-600 bg-gray-100 active dark:text-green-500 dark:bg-gray-800",
85
+ indigo: "text-indigo-600 bg-gray-100 active dark:text-indigo-500 dark:bg-gray-800",
86
+ pink: "text-pink-600 bg-gray-100 active dark:text-pink-500 dark:bg-gray-800",
87
+ purple: "text-purple-600 bg-gray-100 active dark:text-purple-500 dark:bg-gray-800",
88
+ red: "text-red-600 bg-gray-100 active dark:text-red-500 dark:bg-gray-800",
89
+ yellow: "text-yellow-600 bg-gray-100 active dark:text-yellow-500 dark:bg-gray-800",
90
+ gray: "active text-gray-900 bg-gray-100 dark:bg-gray-700 dark:text-white"
91
+ },
92
+ inactive: "bg-white hover:text-gray-700 hover:bg-gray-50 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700",
93
+ disabled: "cursor-not-allowed",
94
+ first: "rounded-s-lg",
95
+ last: "rounded-e-lg",
96
+ middle: ""
97
+ }
98
+ },
99
+ icon: "mr-2 h-5 w-5"
100
+ }
101
+ },
102
+ tabpanel_container: {
103
+ horizontal: "",
104
+ vertical: "w-full"
105
+ },
106
+ tabpanel: {
107
+ horizontal: {
108
+ none: {
109
+ active: "",
110
+ inactive: "hidden"
111
+ },
112
+ default: {
113
+ active: "p-4 rounded-b-lg bg-gray-50 dark:bg-gray-800",
114
+ inactive: "p-4 rounded-b-lg bg-gray-50 dark:bg-gray-800 hidden"
115
+ }
116
+ },
117
+ vertical: {
118
+ none: {
119
+ active: "",
120
+ inactive: "hidden"
121
+ },
122
+ default: {
123
+ active: "p-6 bg-gray-50 text-medium text-gray-500 dark:text-gray-400 dark:bg-gray-800 rounded-lg h-full",
124
+ inactive: "p-6 bg-gray-50 text-medium text-gray-500 dark:text-gray-400 dark:bg-gray-800 rounded-lg h-full hidden"
125
+ }
126
+ }
127
+ }
128
+ }
129
+ end
130
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
131
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::TextComponent
4
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
5
+ mattr_accessor :styles do
6
+ {
7
+ color: {
8
+ blue: "text-blue-600 dark:text-blue-500",
9
+ red: "text-red-600 dark:text-red-500",
10
+ green: "text-green-600 dark:text-green-500",
11
+ yellow: "text-yellow-600 dark:text-yellow-500",
12
+ purple: "text-purple-600 dark:text-purple-500",
13
+ pink: "text-pink-600 dark:text-pink-500",
14
+ orange: "text-orange-600 dark:text-orange-500",
15
+ teal: "text-teal-600 dark:text-teal-500",
16
+ cyan: "text-cyan-600 dark:text-cyan-500",
17
+ indigo: "text-indigo-600 dark:text-indigo-500",
18
+ blue_to_green: "text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400",
19
+ blue_to_purple: "text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-purple-500",
20
+ red_to_yellow: "text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-yellow-500",
21
+ green_to_blue: "text-transparent bg-clip-text bg-gradient-to-r from-green-500 to-blue-500",
22
+ purple_to_pink: "text-transparent bg-clip-text bg-gradient-to-r from-purple-500 to-pink-500",
23
+ orange_to_red: "text-transparent bg-clip-text bg-gradient-to-r from-orange-500 to-red-500"
24
+ },
25
+ bg_color: {
26
+ blue: "px-2 bg-blue-600 rounded-sm dark:bg-blue-500",
27
+ red: "px-2 bg-red-600 rounded-sm dark:bg-red-500",
28
+ green: "px-2 text-white bg-green-600 rounded-sm dark:bg-green-500",
29
+ yellow: "px-2 text-white bg-yellow-600 rounded-sm dark:bg-yellow-500",
30
+ purple: "px-2 text-white bg-purple-600 rounded-sm dark:bg-purple-500",
31
+ pink: "px-2 text-white bg-pink-600 rounded-sm dark:bg-pink-500",
32
+ orange: "px-2 text-white bg-orange-600 rounded-sm dark:bg-orange-500",
33
+ teal: "px-2 text-white bg-teal-600 rounded-sm dark:bg-teal-500",
34
+ cyan: "px-2 text-white bg-cyan-600 rounded-sm dark:bg-cyan-500",
35
+ indigo: "px-2 text-white bg-indigo-600 rounded-sm dark:bg-indigo-500",
36
+ blue_to_purple: "px-2 text-white bg-gradient-to-r from-blue-500 to-purple-500 rounded-sm",
37
+ red_to_yellow: "px-2 text-white bg-gradient-to-r from-red-500 to-yellow-500 rounded-sm",
38
+ green_to_blue: "px-2 text-white bg-gradient-to-r from-green-500 to-blue-500 rounded-sm",
39
+ purple_to_pink: "px-2 text-white bg-gradient-to-r from-purple-500 to-pink-500 rounded-sm",
40
+ orange_to_red: "px-2 text-white bg-gradient-to-r from-orange-500 to-red-500 rounded-sm"
41
+ },
42
+ size: {
43
+ xs: "text-xs",
44
+ sm: "text-sm",
45
+ base: "text-base",
46
+ lg: "text-lg",
47
+ xl: "text-xl",
48
+ "2xl": "text-2xl",
49
+ "3xl": "text-3xl",
50
+ "4xl": "text-4xl",
51
+ "5xl": "text-5xl",
52
+ "6xl": "text-6xl"
53
+ },
54
+ weight: {
55
+ thin: "font-thin",
56
+ extralight: "font-extralight",
57
+ light: "font-light",
58
+ normal: "font-normal",
59
+ medium: "font-medium",
60
+ semibold: "font-semibold",
61
+ bold: "font-bold",
62
+ extrabold: "font-extrabold",
63
+ black: "font-black"
64
+ },
65
+ transform: {
66
+ uppercase: "uppercase",
67
+ lowercase: "lowercase",
68
+ capitalize: "capitalize"
69
+ },
70
+ decoration_line: {
71
+ underline: "underline",
72
+ overline: "overline",
73
+ line_through: "line-through"
74
+ },
75
+ decoration_type: {
76
+ double: "decoration-double",
77
+ dotted: "decoration-dotted",
78
+ dashed: "decoration-dashed",
79
+ wavy: "decoration-wavy"
80
+ },
81
+ decoration_color: {
82
+ blue: "decoration-blue-400 dark:decoration-blue-600",
83
+ red: "decoration-red-400 dark:decoration-red-600",
84
+ green: "decoration-green-400 dark:decoration-green-600",
85
+ yellow: "decoration-yellow-400 dark:decoration-yellow-600",
86
+ purple: "decoration-purple-400 dark:decoration-purple-600",
87
+ pink: "decoration-pink-400 dark:decoration-pink-600",
88
+ orange: "decoration-orange-400 dark:decoration-orange-600",
89
+ teal: "decoration-teal-400 dark:decoration-teal-600",
90
+ cyan: "decoration-cyan-400 dark:decoration-cyan-600",
91
+ indigo: "decoration-indigo-400 dark:decoration-indigo-600"
92
+ },
93
+ decoration_tickness: [
94
+ "decoration-0",
95
+ "decoration-1",
96
+ "decoration-2",
97
+ "decoration-4",
98
+ "decoration-8"
99
+ ],
100
+ underline_offset: [
101
+ "underline-offset-0",
102
+ "underline-offset-1",
103
+ "underline-offset-2",
104
+ "underline-offset-4",
105
+ "underline-offset-8"
106
+ ]
107
+ }
108
+ end
109
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
110
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::TooltipComponent
4
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
5
+ mattr_accessor :styles do
6
+ {
7
+ base: "absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-xs opacity-0 tooltip dark:bg-gray-700"
8
+ }
9
+ end
10
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
11
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit
4
+ module ViewComponents
5
+ # Usage:
6
+ #
7
+ # Run via rake task:
8
+ #
9
+ # bin/rails fluxbit_view_components:detect_legacy_slots
10
+ # bin/rails fluxbit_view_components:migrate_legacy_slots
11
+ # bin/rails fluxbit_view_components:migrate_legacy_slots app/views
12
+ #
13
+ # Or run via rails console if you need to pass custom paths:
14
+ #
15
+ # Fluxbit::ViewComponents::Codemods::V3SlotSetters.new(
16
+ # view_path: Rails.root.join("app/views"),
17
+ # ).call
18
+ module Codemods
19
+ class V3SlotSetters
20
+ TEMPLATE_LANGUAGES = %w[erb slim haml].join(",").freeze
21
+ RENDER_REGEX = /render[( ](?<component>\w+(?:::\w+)*)\.new[) ]+(do|\{) \|(?<arg>\w+)\b/ # standard:disable Lint/MixedRegexpCaptureTypes
22
+
23
+ Suggestion = Struct.new(:file, :line, :message)
24
+
25
+ def initialize(view_component_path: [], view_path: [], migrate: false)
26
+ Rails.application.eager_load!
27
+
28
+ @view_component_path = view_component_path
29
+ @view_path = view_path
30
+ @migrate = migrate
31
+ end
32
+
33
+ def call
34
+ puts "Using ViewComponent path: #{view_component_paths.join(", ")}"
35
+ puts "Using Views path: #{view_paths.join(", ")}"
36
+ puts "#{view_components.size} ViewComponents found"
37
+ puts "#{slottable_components.size} ViewComponents using Slots found"
38
+ puts "#{view_component_files.size} ViewComponent templates found"
39
+ puts "#{view_files.size} view files found"
40
+ process_all_files
41
+ end
42
+
43
+ def process_all_files
44
+ all_files.each do |file|
45
+ process_file(file)
46
+ end
47
+ end
48
+
49
+ def process_file(file)
50
+ @suggestions = []
51
+ @suggestions += scan_exact_matches(file)
52
+ @suggestions += scan_uncertain_matches(file)
53
+
54
+ return unless @suggestions.any?
55
+
56
+ puts
57
+ puts "File: #{file}"
58
+ @suggestions.each do |s|
59
+ puts "=> line #{s.line}: #{s.message}"
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def scan_exact_matches(file)
66
+ [].tap do |suggestions|
67
+ rendered_components = []
68
+ content = File.read(file)
69
+
70
+ if (render_match = content.match(RENDER_REGEX))
71
+ component = render_match[:component]
72
+ arg = render_match[:arg]
73
+
74
+ if registered_slots.key?(component.constantize)
75
+ used_slots_names = registered_slots[component.constantize]
76
+ rendered_components << { component: component, arg: arg, slots: used_slots_names }
77
+ end
78
+ end
79
+
80
+ File.open(file, "r+") do |f|
81
+ lines = []
82
+ f.each_line do |line|
83
+ rendered_components.each do |rendered_component|
84
+ arg = rendered_component[:arg]
85
+ slots = rendered_component[:slots]
86
+
87
+ if (matches = line.scan(/#{arg}\.#{Regexp.union(slots)}/))
88
+ matches.each do |match|
89
+ new_value = match.gsub("#{arg}.", "#{arg}.with_")
90
+ message = if @migrate
91
+ "replaced `#{match}` with `#{new_value}`"
92
+ else
93
+ "probably replace `#{match}` with `#{new_value}`"
94
+ end
95
+ suggestions << Suggestion.new(file, f.lineno, message)
96
+ if @migrate
97
+ line.gsub!("#{arg}.", "#{arg}.with_")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ lines << line
103
+ end
104
+
105
+ if @migrate
106
+ f.rewind
107
+ f.write(lines.join)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def scan_uncertain_matches(file)
114
+ [].tap do |suggestions|
115
+ File.open(file, "r+") do |f|
116
+ lines = []
117
+ f.each_line do |line|
118
+ if (matches = line.scan(/(?<!\s)\.(?<slot>#{Regexp.union(all_registered_slot_names)})\b/))
119
+ matches.flatten.each do |match|
120
+ next if @suggestions.find { |s| s.file == file && s.line == f.lineno }
121
+
122
+ message = if @migrate
123
+ "replaced `#{match}` with `with_#{match}`"
124
+ else
125
+ "maybe replace `#{match}` with `with_#{match}`"
126
+ end
127
+ suggestions << Suggestion.new(file, f.lineno, message)
128
+ if @migrate
129
+ line.gsub!(/(?<!\s)\.(#{match})\b/, ".with_\\1")
130
+ end
131
+ end
132
+ end
133
+ lines << line
134
+ end
135
+
136
+ if @migrate
137
+ f.rewind
138
+ f.write(lines.join)
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def view_components
145
+ ViewComponent::Base.descendants
146
+ end
147
+
148
+ def slottable_components
149
+ view_components.select do |comp|
150
+ comp.registered_slots.any?
151
+ end
152
+ end
153
+
154
+ def registered_slots
155
+ @registered_slots ||= {}.tap do |slots|
156
+ puts
157
+ puts "Detected slots:"
158
+ slottable_components.each do |comp|
159
+ puts "- `#{comp}` has slots: #{comp.registered_slots.keys.join(", ")}"
160
+ slots[comp] = comp.registered_slots.map do |slot_name, slot|
161
+ normalized_slot_name(slot_name, slot)
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ def all_registered_slot_names
168
+ @all_registered_slot_names ||= registered_slots.values.flatten.uniq
169
+ end
170
+
171
+ def view_component_files
172
+ Dir.glob(Pathname.new(File.join(view_component_path_glob, "**", "*.{rb,#{TEMPLATE_LANGUAGES}}")))
173
+ end
174
+
175
+ def view_files
176
+ Dir.glob(Pathname.new(File.join(view_path_glob, "**", "*.{#{TEMPLATE_LANGUAGES}}")))
177
+ end
178
+
179
+ def all_files
180
+ view_component_files + view_files
181
+ end
182
+
183
+ def view_component_paths
184
+ @view_component_paths ||= [
185
+ Rails.application.config.view_component.view_component_path,
186
+ @view_component_path
187
+ ].flatten.compact.uniq
188
+ end
189
+
190
+ def view_component_path_glob
191
+ return view_component_paths.first if view_component_paths.size == 1
192
+
193
+ "{#{view_component_paths.join(",")}}"
194
+ end
195
+
196
+ def rails_view_paths
197
+ ActionController::Base.view_paths.select do |path|
198
+ path.to_s.include?(Rails.root.to_s)
199
+ end.map(&:to_s)
200
+ end
201
+
202
+ def view_paths
203
+ @view_paths ||= [
204
+ rails_view_paths,
205
+ Rails.application.config.view_component.preview_paths,
206
+ @view_path
207
+ ].flatten.compact.uniq
208
+ end
209
+
210
+ def view_path_glob
211
+ return view_paths.first if view_paths.size == 1
212
+
213
+ "{#{view_paths.join(",")}}"
214
+ end
215
+
216
+ def normalized_slot_name(slot_name, slot)
217
+ slot[:collection] ? ActiveSupport::Inflector.singularize(slot_name) : slot_name.to_s
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+ require "view_component"
5
+ require "view_component/version"
6
+
7
+ module Fluxbit
8
+ module ViewComponents
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Fluxbit::ViewComponents
11
+
12
+ config.autoload_paths = %W[
13
+ #{root}/app/components
14
+ #{root}/app/helpers
15
+ ]
16
+
17
+ # Remove default wrapping .field_with_errors for proper Shopify form validations
18
+ config.to_prepare do
19
+ ActionView::Base.field_error_proc = ->(html_tag, _instance) { html_tag.html_safe }
20
+ end
21
+
22
+ initializer "fluxbit_view_components.importmap", before: "importmap" do |app|
23
+ if app.config.respond_to?(:importmap) && app.config.importmap.has_key?(:cache_sweepers)
24
+ app.config.importmap.cache_sweepers << Engine.root.join("app/assets/javascripts")
25
+ end
26
+ end
27
+
28
+ initializer "fluxbit_view_components.helpers" do
29
+ ActiveSupport.on_load(:action_controller_base) do
30
+ helper Fluxbit::ClassesHelper
31
+ helper Fluxbit::ComponentsHelper
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit
4
+ module ViewComponents
5
+ VERSION = "0.1.0"
6
+ end
7
+ end