terrazzo 0.6.0 → 0.7.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -0
  3. data/Rakefile +1 -0
  4. data/app/controllers/terrazzo/application_controller.rb +63 -14
  5. data/app/helpers/terrazzo/collection_actions_helper.rb +38 -13
  6. data/app/helpers/terrazzo/resource_paths_helper.rb +55 -0
  7. data/app/views/terrazzo/application/_edit_base.json.props +7 -4
  8. data/app/views/terrazzo/application/_index_base.json.props +83 -7
  9. data/app/views/terrazzo/application/_navigation.json.props +7 -4
  10. data/app/views/terrazzo/application/_new_base.json.props +6 -3
  11. data/app/views/terrazzo/application/_show_base.json.props +7 -5
  12. data/lib/generators/terrazzo/dashboard/dashboard_generator.rb +48 -24
  13. data/lib/generators/terrazzo/dashboard/templates/controller.rb.erb +8 -4
  14. data/lib/generators/terrazzo/dashboard/templates/dashboard.rb.erb +33 -25
  15. data/lib/generators/terrazzo/eject/eject_generator.rb +223 -113
  16. data/lib/generators/terrazzo/field/field_generator.rb +51 -7
  17. data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +2 -3
  18. data/lib/generators/terrazzo/install/install_generator.rb +641 -4
  19. data/lib/generators/terrazzo/install/templates/admin.css +2 -0
  20. data/lib/generators/terrazzo/install/templates/application.js.erb +3 -0
  21. data/lib/generators/terrazzo/install/templates/components.json.erb +17 -0
  22. data/lib/generators/terrazzo/install/templates/custom_page_mapping.js.erb +5 -0
  23. data/lib/generators/terrazzo/install/templates/generated_page_mapping.js.erb +10 -0
  24. data/lib/generators/terrazzo/install/templates/jsconfig.json +8 -0
  25. data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +9 -1
  26. data/lib/generators/terrazzo/install/templates/superglue.html.erb.erb +2 -2
  27. data/lib/generators/terrazzo/routes/routes_generator.rb +55 -20
  28. data/lib/generators/terrazzo/views/edit_generator.rb +16 -10
  29. data/lib/generators/terrazzo/views/eject_compatibility.rb +19 -0
  30. data/lib/generators/terrazzo/views/field_generator.rb +18 -6
  31. data/lib/generators/terrazzo/views/generated_defaults_helper.rb +110 -0
  32. data/lib/generators/terrazzo/views/index_generator.rb +9 -8
  33. data/lib/generators/terrazzo/views/layout_generator.rb +4 -3
  34. data/lib/generators/terrazzo/views/navigation_generator.rb +5 -1
  35. data/lib/generators/terrazzo/views/new_generator.rb +16 -10
  36. data/lib/generators/terrazzo/views/page_mapping_helper.rb +131 -8
  37. data/lib/generators/terrazzo/views/show_generator.rb +7 -6
  38. data/lib/generators/terrazzo/views/templates/components/CollectionFilters.jsx +25 -0
  39. data/lib/generators/terrazzo/views/templates/components/CollectionItemActions.jsx +68 -0
  40. data/lib/generators/terrazzo/views/templates/components/CollectionToolbarActions.jsx +60 -0
  41. data/lib/generators/terrazzo/views/templates/components/HasManyPagination.jsx +36 -0
  42. data/lib/generators/terrazzo/views/templates/components/Layout.jsx +9 -4
  43. data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +32 -24
  44. data/lib/generators/terrazzo/views/templates/components/ResourceTable.jsx +172 -0
  45. data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +14 -6
  46. data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +7 -5
  47. data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +1 -1
  48. data/lib/generators/terrazzo/views/templates/components/site-header.jsx +2 -2
  49. data/lib/generators/terrazzo/views/templates/fields/asset/FormField.jsx +15 -0
  50. data/lib/generators/terrazzo/views/templates/fields/asset/IndexField.jsx +5 -0
  51. data/lib/generators/terrazzo/views/templates/fields/asset/ShowField.jsx +13 -0
  52. data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +1 -1
  53. data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +1 -1
  54. data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +1 -1
  55. data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +3 -3
  56. data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +4 -3
  57. data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +21 -35
  58. data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +1 -1
  59. data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +5 -5
  60. data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +0 -2
  61. data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +1 -1
  62. data/lib/generators/terrazzo/views/templates/fields/number/IndexField.jsx +1 -1
  63. data/lib/generators/terrazzo/views/templates/fields/number/ShowField.jsx +1 -1
  64. data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +3 -2
  65. data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +2 -2
  66. data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +1 -1
  67. data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +1 -1
  68. data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +2 -2
  69. data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +2 -2
  70. data/lib/generators/terrazzo/views/templates/pages/_collection.jsx +5 -3
  71. data/lib/generators/terrazzo/views/templates/pages/_form.jsx +37 -30
  72. data/lib/generators/terrazzo/views/templates/pages/_navigation.json.props +14 -4
  73. data/lib/generators/terrazzo/views/templates/pages/edit.jsx +6 -4
  74. data/lib/generators/terrazzo/views/templates/pages/index.jsx +20 -8
  75. data/lib/generators/terrazzo/views/templates/pages/new.jsx +8 -4
  76. data/lib/generators/terrazzo/views/templates/pages/show.jsx +30 -32
  77. data/lib/generators/terrazzo/views/views_generator.rb +9 -5
  78. data/lib/terrazzo/base_dashboard.rb +167 -5
  79. data/lib/terrazzo/csv_export.rb +38 -0
  80. data/lib/terrazzo/field/associative.rb +16 -4
  81. data/lib/terrazzo/field/base.rb +4 -0
  82. data/lib/terrazzo/field/belongs_to.rb +1 -2
  83. data/lib/terrazzo/field/deferred.rb +4 -0
  84. data/lib/terrazzo/field/has_many.rb +79 -11
  85. data/lib/terrazzo/field/password.rb +6 -0
  86. data/lib/terrazzo/field/rich_text.rb +13 -0
  87. data/lib/terrazzo/generator_helpers.rb +1 -1
  88. data/lib/terrazzo/has_many_pagination.rb +9 -1
  89. data/lib/terrazzo/namespace/resource.rb +30 -0
  90. data/lib/terrazzo/namespace.rb +26 -1
  91. data/lib/terrazzo/search.rb +29 -9
  92. data/lib/terrazzo/version.rb +1 -1
  93. data/lib/terrazzo.rb +1 -0
  94. data/terrazzo.gemspec +11 -1
  95. metadata +40 -6
  96. data/lib/generators/terrazzo/install/templates/application.json.props.erb +0 -17
  97. data/lib/generators/terrazzo/views/templates/fields/FieldRenderer.jsx +0 -103
  98. data/lib/generators/terrazzo/views/templates/fields/index.js +0 -81
@@ -1,8 +1,11 @@
1
1
  require "rails/generators"
2
+ require_relative "../views/generated_defaults_helper"
2
3
 
3
4
  module Terrazzo
4
5
  module Generators
5
6
  class EjectGenerator < Rails::Generators::Base
7
+ include Views::GeneratedDefaultsHelper
8
+
6
9
  source_root File.expand_path("../views/templates", __dir__)
7
10
 
8
11
  argument :target, type: :string,
@@ -22,9 +25,11 @@ module Terrazzo
22
25
  when "pages"
23
26
  eject_page
24
27
  when "navigation"
28
+ raise_unknown_target unless target == "navigation"
29
+
25
30
  eject_navigation
26
31
  else
27
- say_status :error, "Unknown category '#{category}'. Use fields/, components/, ui/, pages/, or navigation", :red
32
+ raise_unknown_target
28
33
  end
29
34
  end
30
35
 
@@ -42,13 +47,17 @@ module Terrazzo
42
47
  options[:namespace]
43
48
  end
44
49
 
50
+ def raise_unknown_target
51
+ raise Thor::Error,
52
+ "Unknown ejection target '#{target}'. Use fields/, components/, ui/, pages/, or navigation"
53
+ end
54
+
45
55
  def eject_field
46
56
  field_type = component_name
47
57
  source_dir = "fields/#{field_type}"
48
58
 
49
59
  unless File.directory?(File.join(self.class.source_root, source_dir))
50
- say_status :error, "Unknown field type '#{field_type}'", :red
51
- return
60
+ raise Thor::Error, "Unknown field type '#{field_type}'"
52
61
  end
53
62
 
54
63
  %w[IndexField.jsx ShowField.jsx FormField.jsx].each do |file|
@@ -57,25 +66,29 @@ module Terrazzo
57
66
  copy_file source, "app/views/#{namespace_name}/fields/#{field_type}/#{file}"
58
67
  end
59
68
 
60
- # Copy shared dependencies if needed
61
- if field_uses_shared?(field_type)
62
- copy_file "fields/shared/TextInputFormField.jsx",
63
- "app/views/#{namespace_name}/fields/shared/TextInputFormField.jsx"
69
+ field_shared_dependencies(field_type).each do |dependency|
70
+ copy_file_unless_exists "fields/shared/#{dependency}.jsx",
71
+ "app/views/#{namespace_name}/fields/shared/#{dependency}.jsx"
64
72
  end
65
73
 
74
+ ensure_ui_barrel
75
+ ensure_components_barrel
66
76
  update_fields_barrel(field_type)
67
77
  end
68
78
 
69
79
  def eject_component
70
80
  name = component_name
71
- source = "components/#{name}.jsx"
72
81
 
73
- unless File.exist?(File.join(self.class.source_root, source))
74
- say_status :error, "Unknown component '#{name}'", :red
75
- return
82
+ unless component_template_exists?(name)
83
+ raise Thor::Error, "Unknown component '#{name}'"
76
84
  end
77
85
 
78
- copy_file source, "app/views/#{namespace_name}/components/#{name}.jsx"
86
+ copy_component_template(name)
87
+ component_dependencies(name).each do |component|
88
+ copy_component_template_unless_exists(component)
89
+ end
90
+
91
+ ensure_ui_barrel
79
92
  update_components_barrel(name)
80
93
  end
81
94
 
@@ -84,12 +97,15 @@ module Terrazzo
84
97
  source = "components/ui/#{name}.jsx"
85
98
 
86
99
  unless File.exist?(File.join(self.class.source_root, source))
87
- say_status :error, "Unknown UI component '#{name}'", :red
88
- return
100
+ raise Thor::Error, "Unknown UI component '#{name}'"
89
101
  end
90
102
 
91
- copy_file source, "app/views/#{namespace_name}/components/ui/#{name}.jsx"
103
+ copy_ui_template(name)
92
104
  update_ui_barrel(name)
105
+
106
+ ui_dependencies(name).each do |component|
107
+ update_ui_barrel(component) if copy_ui_template_unless_exists(component)
108
+ end
93
109
  end
94
110
 
95
111
  def eject_page
@@ -97,142 +113,236 @@ module Terrazzo
97
113
  source = "pages/#{name}.jsx"
98
114
 
99
115
  unless File.exist?(File.join(self.class.source_root, source))
100
- say_status :error, "Unknown page template '#{name}'", :red
101
- return
116
+ raise Thor::Error, "Unknown page template '#{name}'"
102
117
  end
103
118
 
104
- copy_file source, "app/views/#{namespace_name}/application/#{name}.jsx"
119
+ ensure_page_barrels
120
+ copy_file_over_generated source, "app/views/#{namespace_name}/application/#{name}.jsx",
121
+ generated_content: generated_page_stub(name)
122
+ page_dependencies(name).each do |dependency|
123
+ copy_file_unless_exists "pages/#{dependency}.jsx",
124
+ "app/views/#{namespace_name}/application/#{dependency}.jsx"
125
+ end
126
+ eject_generated_form_counterpart(name)
105
127
  end
106
128
 
107
129
  def eject_navigation
108
- source = File.join(Terrazzo::Engine.root, "app/views/terrazzo/application/_navigation.json.props")
109
130
  dest = "app/views/#{namespace_name}/application/_navigation.json.props"
110
- copy_file source, dest
131
+ copy_file_over_generated "pages/_navigation.json.props", dest,
132
+ generated_content: generated_navigation_partial
111
133
  say "\nNavigation partial ejected to #{dest}.", :green
112
134
  say "Edit it to customize your admin navigation."
113
135
  end
114
136
 
115
- def field_uses_shared?(field_type)
116
- %w[string number email url password date date_time time].include?(field_type)
117
- end
118
-
119
137
  def update_fields_barrel(field_type)
120
- barrel_path = "app/views/#{namespace_name}/fields/index.js"
121
- barrel_file = File.join(destination_root, barrel_path)
122
-
123
138
  type_label = field_type.split("_").map(&:capitalize).join("")
139
+ register_name = "register#{type_label}FieldType"
124
140
  local_exports = <<~JS.strip
125
141
  // #{type_label} - ejected
126
- export { IndexField as #{type_label}IndexField } from "./#{field_type}/IndexField";
127
- export { ShowField as #{type_label}ShowField } from "./#{field_type}/ShowField";
128
- export { FormField as #{type_label}FormField } from "./#{field_type}/FormField";
142
+ import { registerFieldType as #{register_name} } from "terrazzo/fields";
143
+ import { IndexField as #{type_label}IndexField } from "./#{field_type}/IndexField";
144
+ import { ShowField as #{type_label}ShowField } from "./#{field_type}/ShowField";
145
+ import { FormField as #{type_label}FormField } from "./#{field_type}/FormField";
146
+
147
+ #{register_name}("#{field_type}", {
148
+ index: #{type_label}IndexField,
149
+ show: #{type_label}ShowField,
150
+ form: #{type_label}FormField,
151
+ });
152
+
153
+ export { #{type_label}IndexField, #{type_label}ShowField, #{type_label}FormField };
129
154
  JS
130
155
 
131
- if File.exist?(barrel_file)
132
- content = File.read(barrel_file)
133
-
134
- # If barrel is still the default re-export-all, replace with explicit exports
135
- if content.include?('export * from "terrazzo/fields"')
136
- new_content = build_fields_barrel_with_ejection(field_type)
137
- create_file barrel_path, new_content, force: true
138
- else
139
- # Barrel already has explicit exports; replace the terrazzo re-export
140
- # for this field type with local imports
141
- unless content.include?("./#{field_type}/")
142
- # Remove the existing terrazzo re-export line for this field type
143
- terrazzo_export = "export { #{type_label}IndexField, #{type_label}ShowField, #{type_label}FormField } from \"terrazzo/fields\";"
144
- if content.include?(terrazzo_export)
145
- new_content = content.sub(terrazzo_export, local_exports)
146
- create_file barrel_path, new_content, force: true
147
- else
148
- append_to_file barrel_path, "\n#{local_exports}\n"
149
- end
150
- end
151
- end
152
- end
156
+ ensure_fields_barrel
157
+ append_to_barrel(fields_barrel_path, local_exports, "./#{field_type}/IndexField")
153
158
  end
154
159
 
155
- def build_fields_barrel_with_ejection(ejected_field_type)
156
- all_field_types = %w[
157
- string text number boolean date date_time time email url password
158
- select rich_text belongs_to has_many has_one polymorphic hstore
159
- ]
160
+ def update_components_barrel(name)
161
+ export_name = component_export_name(name)
160
162
 
161
- lines = ['export { FieldRenderer, registerFieldType } from "terrazzo/fields";', ""]
163
+ local_exports = if name == "Layout"
164
+ <<~JS.strip
165
+ // Layout - ejected
166
+ import { setLayout } from "terrazzo";
167
+ import { Layout } from "./Layout";
162
168
 
163
- all_field_types.each do |ft|
164
- label = ft.split("_").map(&:capitalize).join("")
165
- if ft == ejected_field_type
166
- lines << "// #{label} - ejected"
167
- lines << "export { IndexField as #{label}IndexField } from \"./#{ft}/IndexField\";"
168
- lines << "export { ShowField as #{label}ShowField } from \"./#{ft}/ShowField\";"
169
- lines << "export { FormField as #{label}FormField } from \"./#{ft}/FormField\";"
170
- else
171
- lines << "export { #{label}IndexField, #{label}ShowField, #{label}FormField } from \"terrazzo/fields\";"
172
- end
169
+ setLayout(Layout);
170
+
171
+ export { Layout };
172
+ JS
173
+ else
174
+ <<~JS.strip
175
+ // #{export_name} - ejected
176
+ import { registerComponent as register#{export_name}Component } from "terrazzo/components";
177
+ import { #{export_name} } from "./#{name}";
178
+
179
+ register#{export_name}Component("#{export_name}", #{export_name});
180
+
181
+ export { #{export_name} } from "./#{name}";
182
+ JS
173
183
  end
174
184
 
175
- lines.join("\n") + "\n"
185
+ ensure_components_barrel
186
+ append_to_barrel(components_barrel_path, local_exports, "./#{name}")
176
187
  end
177
188
 
178
- def update_components_barrel(name)
179
- barrel_path = "app/views/#{namespace_name}/components/index.js"
180
- barrel_file = File.join(destination_root, barrel_path)
181
-
182
- return unless File.exist?(barrel_file)
189
+ def update_ui_barrel(name)
190
+ local_exports = ui_component_exports(name)
191
+ ensure_ui_barrel
192
+ append_to_barrel(ui_barrel_path, "// #{name} - ejected\nexport { #{local_exports} } from \"./#{name}\";", "./#{name}")
193
+ end
183
194
 
184
- content = File.read(barrel_file)
185
- if content.include?('export * from "terrazzo/components"')
186
- export_name = component_export_name(name)
187
- new_content = build_components_barrel_with_ejection(name, export_name)
188
- create_file barrel_path, new_content, force: true
195
+ def component_export_name(file_name)
196
+ case file_name
197
+ when "app-sidebar" then "AppSidebar"
198
+ when "site-header" then "SiteHeader"
199
+ else file_name
189
200
  end
190
201
  end
191
202
 
192
- def build_components_barrel_with_ejection(ejected_name, export_name)
193
- all_components = {
194
- "Layout" => "Layout",
195
- "app-sidebar" => "AppSidebar",
196
- "site-header" => "SiteHeader",
197
- "FlashMessages" => "FlashMessages",
198
- "SearchBar" => "SearchBar",
199
- "Pagination" => "Pagination",
200
- "SortableHeader" => "SortableHeader",
201
- }
202
-
203
- lines = []
204
- all_components.each do |file_name, export|
205
- if file_name == ejected_name
206
- lines << "export { #{export} } from \"./#{file_name}\"; // ejected"
207
- else
208
- lines << "export { #{export} } from \"terrazzo/components\";"
203
+ def component_template_exists?(name)
204
+ File.exist?(File.join(self.class.source_root, "components/#{name}.jsx"))
205
+ end
206
+
207
+ def copy_component_template(name)
208
+ copy_file "components/#{name}.jsx", "app/views/#{namespace_name}/components/#{name}.jsx"
209
+ end
210
+
211
+ def copy_component_template_unless_exists(name)
212
+ copy_file_unless_exists "components/#{name}.jsx", "app/views/#{namespace_name}/components/#{name}.jsx"
213
+ end
214
+
215
+ def copy_ui_template(name)
216
+ copy_file "components/ui/#{name}.jsx", "app/views/#{namespace_name}/components/ui/#{name}.jsx"
217
+ end
218
+
219
+ def copy_ui_template_unless_exists(name)
220
+ destination = "app/views/#{namespace_name}/components/ui/#{name}.jsx"
221
+ existed = File.exist?(File.join(destination_root, destination))
222
+ copy_file_unless_exists "components/ui/#{name}.jsx", destination
223
+ !existed
224
+ end
225
+
226
+ def eject_generated_form_counterpart(name)
227
+ counterpart = { "edit" => "new", "new" => "edit" }[name]
228
+ return unless counterpart
229
+
230
+ destination = "app/views/#{namespace_name}/application/#{counterpart}.jsx"
231
+ return unless generated_file?(destination, generated_page_stub(counterpart))
232
+
233
+ copy_file "pages/#{counterpart}.jsx", destination, force: true
234
+ end
235
+
236
+ def field_shared_dependencies(field_type)
237
+ field_template_paths(field_type).flat_map do |path|
238
+ relative_imports(File.read(path)).filter_map do |specifier|
239
+ next unless specifier.start_with?("../shared/")
240
+
241
+ dependency = File.basename(specifier, ".*")
242
+ next unless template_exists?("fields/shared/#{dependency}.jsx")
243
+
244
+ dependency
209
245
  end
210
- end
246
+ end.uniq
247
+ end
211
248
 
212
- lines.join("\n") + "\n"
249
+ def component_dependencies(name)
250
+ template_dependencies("components/#{name}.jsx", "components")
213
251
  end
214
252
 
215
- def update_ui_barrel(name)
216
- barrel_path = "app/views/#{namespace_name}/components/ui/index.js"
253
+ def page_dependencies(name)
254
+ template_dependencies("pages/#{name}.jsx", "pages")
255
+ end
256
+
257
+ def ui_dependencies(name)
258
+ template_dependencies("components/ui/#{name}.jsx", "components/ui")
259
+ end
260
+
261
+ def template_dependencies(template, template_root, seen = [])
262
+ template_path = File.join(self.class.source_root, template)
263
+ return [] unless File.exist?(template_path)
264
+
265
+ relative_imports(File.read(template_path)).flat_map do |specifier|
266
+ next unless specifier.start_with?("./")
267
+
268
+ dependency = File.basename(specifier.delete_prefix("./"), ".*")
269
+ dependency_template = "#{template_root}/#{dependency}.jsx"
270
+ next unless template_exists?(dependency_template)
271
+ next if seen.include?(dependency)
272
+
273
+ [dependency, *template_dependencies(dependency_template, template_root, seen + [dependency])]
274
+ end.compact.uniq
275
+ end
276
+
277
+ def field_template_paths(field_type)
278
+ Dir[File.join(self.class.source_root, "fields", field_type, "*.jsx")]
279
+ end
280
+
281
+ def relative_imports(source)
282
+ source.scan(/from\s+["']([^"']+)["']/).flatten
283
+ end
284
+
285
+ def template_exists?(template)
286
+ File.exist?(File.join(self.class.source_root, template))
287
+ end
288
+
289
+ def ui_component_exports(name)
290
+ ui_barrel_template = File.read(File.join(self.class.source_root, "components/ui/index.js"))
291
+ match = ui_barrel_template.match(/^export\s*\{(?<exports>[^}]*)\}\s*from\s*["']\.\/#{Regexp.escape(name)}["'];/m)
292
+ raise Thor::Error, "Could not find UI exports for '#{name}'" unless match
293
+
294
+ match[:exports].split(",").map(&:strip).reject(&:empty?).join(", ")
295
+ end
296
+
297
+ def ensure_barrel(barrel_path, package_export)
217
298
  barrel_file = File.join(destination_root, barrel_path)
218
299
 
219
- return unless File.exist?(barrel_file)
300
+ unless File.exist?(barrel_file)
301
+ create_file barrel_path, "#{package_export}\n"
302
+ return
303
+ end
220
304
 
221
305
  content = File.read(barrel_file)
222
- if content.include?('export * from "terrazzo/ui"')
223
- new_content = "export * from \"terrazzo/ui\";\n"
224
- new_content += "// Override ejected UI component:\n"
225
- new_content += "export * from \"./#{name}\";\n"
226
- create_file barrel_path, new_content, force: true
227
- end
306
+ return if content.include?(package_export)
307
+
308
+ prepend_to_file barrel_path, "#{package_export}\n"
228
309
  end
229
310
 
230
- def component_export_name(file_name)
231
- case file_name
232
- when "app-sidebar" then "AppSidebar"
233
- when "site-header" then "SiteHeader"
234
- else file_name
235
- end
311
+ def ensure_page_barrels
312
+ ensure_fields_barrel
313
+ ensure_components_barrel
314
+ ensure_ui_barrel
315
+ end
316
+
317
+ def ensure_fields_barrel
318
+ ensure_barrel(fields_barrel_path, 'export * from "terrazzo/fields";')
319
+ end
320
+
321
+ def ensure_components_barrel
322
+ ensure_barrel(components_barrel_path, 'export * from "terrazzo/components";')
323
+ end
324
+
325
+ def ensure_ui_barrel
326
+ ensure_barrel(ui_barrel_path, 'export * from "terrazzo/ui";')
327
+ end
328
+
329
+ def fields_barrel_path
330
+ "app/views/#{namespace_name}/fields/index.js"
331
+ end
332
+
333
+ def components_barrel_path
334
+ "app/views/#{namespace_name}/components/index.js"
335
+ end
336
+
337
+ def ui_barrel_path
338
+ "app/views/#{namespace_name}/components/ui/index.js"
339
+ end
340
+
341
+ def append_to_barrel(barrel_path, content, marker)
342
+ barrel_file = File.join(destination_root, barrel_path)
343
+ return if File.exist?(barrel_file) && File.read(barrel_file).include?(marker)
344
+
345
+ append_to_file barrel_path, "\n#{content}\n"
236
346
  end
237
347
  end
238
348
  end
@@ -10,7 +10,7 @@ module Terrazzo
10
10
 
11
11
  def create_field_class
12
12
  template "field.rb.erb",
13
- "app/fields/#{file_name}_field.rb"
13
+ "app/fields/terrazzo/field/#{file_name}.rb"
14
14
  end
15
15
 
16
16
  def create_jsx_components
@@ -21,18 +21,32 @@ module Terrazzo
21
21
  end
22
22
 
23
23
  def register_in_barrel
24
- barrel_path = File.join(destination_root, "app/views/#{namespace_name}/fields/index.js")
25
- return unless File.exist?(barrel_path)
24
+ ensure_fields_barrel
25
+ ensure_ui_barrel
26
26
 
27
+ barrel_path = "app/views/#{namespace_name}/fields/index.js"
28
+ barrel_file = File.join(destination_root, barrel_path)
29
+ return if File.read(barrel_file).include?("./#{file_name}/IndexField")
30
+
31
+ register_name = "register#{class_name}FieldType"
27
32
  registration = <<~JS
28
33
 
29
34
  // #{class_name} - custom field
30
- export { IndexField as #{class_name}IndexField } from "./#{file_name}/IndexField";
31
- export { ShowField as #{class_name}ShowField } from "./#{file_name}/ShowField";
32
- export { FormField as #{class_name}FormField } from "./#{file_name}/FormField";
35
+ import { registerFieldType as #{register_name} } from "terrazzo/fields";
36
+ import { IndexField as #{class_name}IndexField } from "./#{file_name}/IndexField";
37
+ import { ShowField as #{class_name}ShowField } from "./#{file_name}/ShowField";
38
+ import { FormField as #{class_name}FormField } from "./#{file_name}/FormField";
39
+
40
+ #{register_name}("#{file_name}", {
41
+ index: #{class_name}IndexField,
42
+ show: #{class_name}ShowField,
43
+ form: #{class_name}FormField,
44
+ });
45
+
46
+ export { #{class_name}IndexField, #{class_name}ShowField, #{class_name}FormField };
33
47
  JS
34
48
 
35
- append_to_file "app/views/#{namespace_name}/fields/index.js", registration
49
+ append_to_file barrel_path, registration
36
50
 
37
51
  say "\nCustom field '#{file_name}' registered in fields/index.js.", :green
38
52
  say "Use it in your dashboard:"
@@ -44,6 +58,36 @@ module Terrazzo
44
58
  def namespace_name
45
59
  options[:namespace]
46
60
  end
61
+
62
+ def ensure_fields_barrel
63
+ barrel_path = "app/views/#{namespace_name}/fields/index.js"
64
+ barrel_file = File.join(destination_root, barrel_path)
65
+
66
+ unless File.exist?(barrel_file)
67
+ create_file barrel_path, %(export * from "terrazzo/fields";\n)
68
+ return
69
+ end
70
+
71
+ content = File.read(barrel_file)
72
+ return if content.include?('export * from "terrazzo/fields";')
73
+
74
+ prepend_to_file barrel_path, %(export * from "terrazzo/fields";\n)
75
+ end
76
+
77
+ def ensure_ui_barrel
78
+ barrel_path = "app/views/#{namespace_name}/components/ui/index.js"
79
+ barrel_file = File.join(destination_root, barrel_path)
80
+
81
+ unless File.exist?(barrel_file)
82
+ create_file barrel_path, %(export * from "terrazzo/ui";\n)
83
+ return
84
+ end
85
+
86
+ content = File.read(barrel_file)
87
+ return if content.include?('export * from "terrazzo/ui";')
88
+
89
+ prepend_to_file barrel_path, %(export * from "terrazzo/ui";\n)
90
+ end
47
91
  end
48
92
  end
49
93
  end
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Input, Label } from "terrazzo/ui";
2
+ import { Input, Label } from "../../components/ui";
3
3
 
4
4
  export function FormField({ attribute, label, value, input, required }) {
5
5
  return (
@@ -9,9 +9,8 @@ export function FormField({ attribute, label, value, input, required }) {
9
9
  </Label>
10
10
  <Input
11
11
  type="text"
12
- id={input.id}
13
- name={input.name}
14
12
  defaultValue={value ?? ""}
13
+ {...input}
15
14
  />
16
15
  </div>
17
16
  );