terrazzo 0.2.2 → 0.3.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/terrazzo/application_controller.rb +7 -1
  3. data/app/views/terrazzo/application/_navigation.json.props +12 -0
  4. data/app/views/terrazzo/application/edit.json.props +0 -8
  5. data/app/views/terrazzo/application/index.json.props +0 -9
  6. data/app/views/terrazzo/application/new.json.props +0 -8
  7. data/app/views/terrazzo/application/show.json.props +0 -9
  8. data/lib/generators/terrazzo/eject/eject_generator.rb +232 -0
  9. data/lib/generators/terrazzo/field/field_generator.rb +16 -9
  10. data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +1 -2
  11. data/lib/generators/terrazzo/install/install_generator.rb +1 -1
  12. data/lib/generators/terrazzo/install/templates/admin.css +1 -0
  13. data/lib/generators/terrazzo/install/templates/{application.json.props → application.json.props.tt} +1 -0
  14. data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +4 -15
  15. data/lib/generators/terrazzo/views/templates/components/Layout.jsx +1 -1
  16. data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +3 -3
  17. data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +1 -1
  18. data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +1 -1
  19. data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +23 -22
  20. data/lib/generators/terrazzo/views/templates/components/site-header.jsx +2 -2
  21. data/lib/generators/terrazzo/views/templates/components/ui/index.js +1 -1
  22. data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +1 -1
  23. data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +1 -1
  24. data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +1 -1
  25. data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +3 -3
  26. data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +1 -1
  27. data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +17 -5
  28. data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +1 -1
  29. data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +3 -3
  30. data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +1 -1
  31. data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +1 -1
  32. data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +1 -1
  33. data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +2 -2
  34. data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +1 -1
  35. data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +1 -1
  36. data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +2 -2
  37. data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +2 -2
  38. data/lib/generators/terrazzo/views/templates/pages/_form.jsx +2 -2
  39. data/lib/generators/terrazzo/views/templates/pages/_navigation.json.props +5 -0
  40. data/lib/generators/terrazzo/views/templates/pages/edit.jsx +2 -3
  41. data/lib/generators/terrazzo/views/templates/pages/index.jsx +3 -14
  42. data/lib/generators/terrazzo/views/templates/pages/new.jsx +2 -3
  43. data/lib/generators/terrazzo/views/templates/pages/show.jsx +3 -4
  44. data/lib/generators/terrazzo/views/views_generator.rb +45 -26
  45. data/lib/terrazzo/field/associative.rb +1 -1
  46. data/lib/terrazzo/field/belongs_to.rb +2 -2
  47. data/lib/terrazzo/field/has_many.rb +12 -5
  48. data/lib/terrazzo/field/has_one.rb +2 -2
  49. data/lib/terrazzo/field/polymorphic.rb +3 -3
  50. data/lib/terrazzo/version.rb +1 -1
  51. data/terrazzo.gemspec +1 -1
  52. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b55338dd365f9ad475e3892c34a4a5ca706625b50bd331dd6793bbd756ec75e7
4
- data.tar.gz: a5193fda7a10ca72103816e0f47a58ba7facf375003a4af22efa0385914378b2
3
+ metadata.gz: a9ba7563c1db759831717790db2c022eb3f2d343c3f393f6679e164c250f8fc5
4
+ data.tar.gz: a985e9f57b97f224b26a58cd72d9e580666d0aae1b16efd8e722ce14460ad860
5
5
  SHA512:
6
- metadata.gz: d048907fe5b71cee9100ce4cd7a1b606e666972be105a39f15c24f3119531deccc4bc7cb85144b65623faf933dbebfd1f78d61b841909872046afe754c3ea71f
7
- data.tar.gz: 6eb4e86c0a06cd790094cc0829313894f2c9d7b6ce232327a5c46610d9d507c5215b87ec81c3a136c0e5ac2b6841c22de4f81b0544a46aba2846a1f8e762f009
6
+ metadata.gz: 877fed40c106cc9ed5443197322033abd8c4d51c5fa69cb596d63ca77b355ffeb4c3235a13125a6bb6c7f1f28a747cab2c2674ebf08fee6bb4a2c7f225fc1eed
7
+ data.tar.gz: 24cdb50150d40149fda75e5fe376978222ab3b200247a006989cb52e98ea2e9482747ab49b3880f7557cb072ca089691f7468f3573a778eaec764bcf45b6cf3c
@@ -196,9 +196,15 @@ module Terrazzo
196
196
  # Build a page identifier that matches the user's namespace, not the
197
197
  # engine's internal template path. The React page-to-component mapping
198
198
  # keys off this identifier (e.g. "admin/application/index").
199
+ #
200
+ # Map create → new and update → edit so that failed validations
201
+ # (which render :new / :edit) resolve to the correct React component.
202
+ TERRAZZO_ACTION_MAP = { "create" => "new", "update" => "edit" }.freeze
203
+
199
204
  def terrazzo_page_identifier
200
205
  ns = controller_path.split("/").first
201
- "#{ns}/application/#{action_name}"
206
+ mapped_action = TERRAZZO_ACTION_MAP[action_name] || action_name
207
+ "#{ns}/application/#{mapped_action}"
202
208
  end
203
209
 
204
210
  private
@@ -0,0 +1,12 @@
1
+ resources = Terrazzo::Namespace.new(namespace).resources_with_index_route
2
+
3
+ json.array! [{ label: "Resources", resources: resources }] do |group|
4
+ json.label group[:label]
5
+ json.items do
6
+ json.array! group[:resources] do |r|
7
+ json.label r.resource_name.humanize.pluralize
8
+ json.path url_for(controller: "/#{r.controller_path}", action: :index, only_path: true)
9
+ json.active r.controller_path == controller_path
10
+ end
11
+ end
12
+ end
@@ -60,11 +60,3 @@ rescue ActionController::UrlGenerationError
60
60
  nil
61
61
  end
62
62
  json.resourceName resource_name
63
-
64
- json.navigation do
65
- json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
66
- json.label nav_resource.resource_name.humanize.pluralize
67
- json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
68
- json.active nav_resource.controller_path == controller_path
69
- end
70
- end
@@ -42,7 +42,6 @@ json.table do
42
42
  json.attribute attr.to_s
43
43
  json.fieldType field.field_type
44
44
  json.value field.serialize_value(:index)
45
- json.options field.serializable_options
46
45
 
47
46
  if field.class.associative? && field.data.present?
48
47
  json.showPath polymorphic_path([namespace, field.data]) rescue nil
@@ -91,11 +90,3 @@ end
91
90
  json.resourceName resource_name.pluralize
92
91
  json.singularResourceName resource_name
93
92
  json.newResourcePath new_polymorphic_path([namespace, resource_class]) rescue nil
94
-
95
- json.navigation do
96
- json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
97
- json.label nav_resource.resource_name.humanize.pluralize
98
- json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
99
- json.active nav_resource.controller_path == controller_path
100
- end
101
- end
@@ -55,11 +55,3 @@ rescue ActionController::UrlGenerationError
55
55
  nil
56
56
  end
57
57
  json.resourceName resource_name
58
-
59
- json.navigation do
60
- json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
61
- json.label nav_resource.resource_name.humanize.pluralize
62
- json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
63
- json.active nav_resource.controller_path == controller_path
64
- end
65
- end
@@ -5,7 +5,6 @@ show_field_json = ->(json, field) do
5
5
  json.label field.attribute.to_s.humanize
6
6
  json.fieldType field.field_type
7
7
  json.value field.serialize_value(:show)
8
- json.options field.serializable_options
9
8
 
10
9
  if field.class.associative? && field.data.present?
11
10
  if field.is_a?(Terrazzo::Field::HasMany)
@@ -44,11 +43,3 @@ rescue ActionController::UrlGenerationError
44
43
  end
45
44
  json.resourceName resource_name
46
45
  json.pluralResourceName resource_name.pluralize
47
-
48
- json.navigation do
49
- json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
50
- json.label nav_resource.resource_name.humanize.pluralize
51
- json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
52
- json.active nav_resource.controller_path == controller_path
53
- end
54
- end
@@ -0,0 +1,232 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ class EjectGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../views/templates", __dir__)
7
+
8
+ argument :target, type: :string,
9
+ desc: "What to eject (e.g., fields/string, components/Layout, ui/button, pages/index)"
10
+
11
+ class_option :namespace, type: :string, default: "admin",
12
+ desc: "Admin namespace"
13
+
14
+ def eject
15
+ case category
16
+ when "fields"
17
+ eject_field
18
+ when "components"
19
+ eject_component
20
+ when "ui"
21
+ eject_ui
22
+ when "pages"
23
+ eject_page
24
+ when "navigation"
25
+ eject_navigation
26
+ else
27
+ say_status :error, "Unknown category '#{category}'. Use fields/, components/, ui/, pages/, or navigation", :red
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def category
34
+ target.split("/").first
35
+ end
36
+
37
+ def component_name
38
+ target.split("/", 2).last
39
+ end
40
+
41
+ def namespace_name
42
+ options[:namespace]
43
+ end
44
+
45
+ def eject_field
46
+ field_type = component_name
47
+ source_dir = "fields/#{field_type}"
48
+
49
+ unless File.directory?(File.join(self.class.source_root, source_dir))
50
+ say_status :error, "Unknown field type '#{field_type}'", :red
51
+ return
52
+ end
53
+
54
+ %w[IndexField.jsx ShowField.jsx FormField.jsx].each do |file|
55
+ source = File.join(source_dir, file)
56
+ next unless File.exist?(File.join(self.class.source_root, source))
57
+ copy_file source, "app/views/#{namespace_name}/fields/#{field_type}/#{file}"
58
+ end
59
+
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"
64
+ end
65
+
66
+ update_fields_barrel(field_type)
67
+ end
68
+
69
+ def eject_component
70
+ name = component_name
71
+ source = "components/#{name}.jsx"
72
+
73
+ unless File.exist?(File.join(self.class.source_root, source))
74
+ say_status :error, "Unknown component '#{name}'", :red
75
+ return
76
+ end
77
+
78
+ copy_file source, "app/views/#{namespace_name}/components/#{name}.jsx"
79
+ update_components_barrel(name)
80
+ end
81
+
82
+ def eject_ui
83
+ name = component_name
84
+ source = "components/ui/#{name}.jsx"
85
+
86
+ unless File.exist?(File.join(self.class.source_root, source))
87
+ say_status :error, "Unknown UI component '#{name}'", :red
88
+ return
89
+ end
90
+
91
+ copy_file source, "app/views/#{namespace_name}/components/ui/#{name}.jsx"
92
+ update_ui_barrel(name)
93
+ end
94
+
95
+ def eject_page
96
+ name = component_name
97
+ source = "pages/#{name}.jsx"
98
+
99
+ unless File.exist?(File.join(self.class.source_root, source))
100
+ say_status :error, "Unknown page template '#{name}'", :red
101
+ return
102
+ end
103
+
104
+ copy_file source, "app/views/#{namespace_name}/application/#{name}.jsx"
105
+ end
106
+
107
+ def eject_navigation
108
+ source = File.join(Terrazzo::Engine.root, "app/views/terrazzo/application/_navigation.json.props")
109
+ dest = "app/views/#{namespace_name}/application/_navigation.json.props"
110
+ copy_file source, dest
111
+ say "\nNavigation partial ejected to #{dest}.", :green
112
+ say "Edit it to customize your admin navigation."
113
+ end
114
+
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
+ 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
+ type_label = field_type.split("_").map(&:capitalize).join("")
124
+ local_exports = <<~JS.strip
125
+ // #{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";
129
+ JS
130
+
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 line for this field type
140
+ # by inserting local exports before the terrazzo re-exports
141
+ unless content.include?("./#{field_type}/")
142
+ append_to_file barrel_path, "\n#{local_exports}\n"
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def build_fields_barrel_with_ejection(ejected_field_type)
149
+ all_field_types = %w[
150
+ string text number boolean date date_time time email url password
151
+ select rich_text belongs_to has_many has_one polymorphic hstore
152
+ ]
153
+
154
+ lines = ['export { FieldRenderer, registerFieldType } from "terrazzo/fields";', ""]
155
+
156
+ all_field_types.each do |ft|
157
+ label = ft.split("_").map(&:capitalize).join("")
158
+ if ft == ejected_field_type
159
+ lines << "// #{label} - ejected"
160
+ lines << "export { IndexField as #{label}IndexField } from \"./#{ft}/IndexField\";"
161
+ lines << "export { ShowField as #{label}ShowField } from \"./#{ft}/ShowField\";"
162
+ lines << "export { FormField as #{label}FormField } from \"./#{ft}/FormField\";"
163
+ else
164
+ lines << "export { #{label}IndexField, #{label}ShowField, #{label}FormField } from \"terrazzo/fields\";"
165
+ end
166
+ end
167
+
168
+ lines.join("\n") + "\n"
169
+ end
170
+
171
+ def update_components_barrel(name)
172
+ barrel_path = "app/views/#{namespace_name}/components/index.js"
173
+ barrel_file = File.join(destination_root, barrel_path)
174
+
175
+ return unless File.exist?(barrel_file)
176
+
177
+ content = File.read(barrel_file)
178
+ if content.include?('export * from "terrazzo/components"')
179
+ export_name = component_export_name(name)
180
+ new_content = build_components_barrel_with_ejection(name, export_name)
181
+ create_file barrel_path, new_content, force: true
182
+ end
183
+ end
184
+
185
+ def build_components_barrel_with_ejection(ejected_name, export_name)
186
+ all_components = {
187
+ "Layout" => "Layout",
188
+ "app-sidebar" => "AppSidebar",
189
+ "site-header" => "SiteHeader",
190
+ "FlashMessages" => "FlashMessages",
191
+ "SearchBar" => "SearchBar",
192
+ "Pagination" => "Pagination",
193
+ "SortableHeader" => "SortableHeader",
194
+ }
195
+
196
+ lines = []
197
+ all_components.each do |file_name, export|
198
+ if file_name == ejected_name
199
+ lines << "export { #{export} } from \"./#{file_name}\"; // ejected"
200
+ else
201
+ lines << "export { #{export} } from \"terrazzo/components\";"
202
+ end
203
+ end
204
+
205
+ lines.join("\n") + "\n"
206
+ end
207
+
208
+ def update_ui_barrel(name)
209
+ barrel_path = "app/views/#{namespace_name}/components/ui/index.js"
210
+ barrel_file = File.join(destination_root, barrel_path)
211
+
212
+ return unless File.exist?(barrel_file)
213
+
214
+ content = File.read(barrel_file)
215
+ if content.include?('export * from "terrazzo/ui"')
216
+ new_content = "export * from \"terrazzo/ui\";\n"
217
+ new_content += "// Override ejected UI component:\n"
218
+ new_content += "export * from \"./#{name}\";\n"
219
+ create_file barrel_path, new_content, force: true
220
+ end
221
+ end
222
+
223
+ def component_export_name(file_name)
224
+ case file_name
225
+ when "app-sidebar" then "AppSidebar"
226
+ when "site-header" then "SiteHeader"
227
+ else file_name
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -20,15 +20,22 @@ module Terrazzo
20
20
  end
21
21
  end
22
22
 
23
- def show_registration_instructions
24
- say "\nTo use your custom field, register it in your FieldRenderer.jsx:", :green
25
- say " import { IndexField as #{class_name}Index } from \"./#{file_name}/IndexField\";"
26
- say " import { ShowField as #{class_name}Show } from \"./#{file_name}/ShowField\";"
27
- say " import { FormField as #{class_name}Form } from \"./#{file_name}/FormField\";"
28
- say ""
29
- say " registerFieldType(\"#{file_name}\", { index: #{class_name}Index, show: #{class_name}Show, form: #{class_name}Form });"
30
- say ""
31
- say "Then use it in your dashboard:"
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)
26
+
27
+ registration = <<~JS
28
+
29
+ // #{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";
33
+ JS
34
+
35
+ append_to_file "app/views/#{namespace_name}/fields/index.js", registration
36
+
37
+ say "\nCustom field '#{file_name}' registered in fields/index.js.", :green
38
+ say "Use it in your dashboard:"
32
39
  say " #{file_name}: Terrazzo::Field::#{class_name},"
33
40
  end
34
41
 
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
- import { Input } from "../../components/ui/input";
3
- import { Label } from "../../components/ui/label";
2
+ import { Input, Label } from "terrazzo/ui";
4
3
 
5
4
  export function FormField({ attribute, label, value, input, required }) {
6
5
  return (
@@ -21,7 +21,7 @@ module Terrazzo
21
21
  end
22
22
 
23
23
  def create_json_props_layout
24
- copy_file "application.json.props",
24
+ template "application.json.props.tt",
25
25
  "app/views/layouts/#{namespace_name}/application.json.props"
26
26
  end
27
27
 
@@ -1,4 +1,5 @@
1
1
  @import "tailwindcss";
2
+ @source "../../../node_modules/terrazzo/dist";
2
3
 
3
4
  @custom-variant dark (&:is(.dark *));
4
5
 
@@ -1,6 +1,7 @@
1
1
  path = request.format.json? ? param_to_dig_path(params[:props_at]) : nil
2
2
 
3
3
  json.data(dig: path) do
4
+ json.navigation(partial: ["<%= namespace_name %>/application/navigation"]) {}
4
5
  yield
5
6
  end
6
7
 
@@ -1,17 +1,7 @@
1
- <% if vite? -%>
2
- const pages = import.meta.glob("../../views/<%= namespace_name %>/**/*.jsx", { eager: true })
3
-
4
- export const pageToPageMapping = {}
5
-
6
- for (const key in pages) {
7
- const identifier = key.replace("../../views/", "").replace(/\.jsx$/, "")
8
- pageToPageMapping[identifier] = pages[key].default
9
- }
10
- <% else -%>
11
- import AdminIndex from "../../views/<%= namespace_name %>/application/index"
12
- import AdminShow from "../../views/<%= namespace_name %>/application/show"
13
- import AdminNew from "../../views/<%= namespace_name %>/application/new"
14
- import AdminEdit from "../../views/<%= namespace_name %>/application/edit"
1
+ import AdminIndex from "../../views/<%= namespace_name %>/application/index";
2
+ import AdminShow from "../../views/<%= namespace_name %>/application/show";
3
+ import AdminNew from "../../views/<%= namespace_name %>/application/new";
4
+ import AdminEdit from "../../views/<%= namespace_name %>/application/edit";
15
5
 
16
6
  export const pageToPageMapping = {
17
7
  '<%= namespace_name %>/application/index': AdminIndex,
@@ -19,4 +9,3 @@ export const pageToPageMapping = {
19
9
  '<%= namespace_name %>/application/new': AdminNew,
20
10
  '<%= namespace_name %>/application/edit': AdminEdit,
21
11
  }
22
- <% end -%>
@@ -3,7 +3,7 @@ import React from "react";
3
3
  import { AppSidebar } from "./app-sidebar";
4
4
  import { SiteHeader } from "./site-header";
5
5
  import { FlashMessages } from "./FlashMessages";
6
- import { SidebarProvider, SidebarInset } from "./ui/sidebar";
6
+ import { SidebarProvider, SidebarInset } from "terrazzo/ui";
7
7
 
8
8
  export function Layout({ navigation, title, actions, children }) {
9
9
  return (
@@ -1,14 +1,14 @@
1
1
  import React, { useContext } from "react";
2
2
  import { NavigationContext } from "@thoughtbot/superglue";
3
3
 
4
- import { Field, FieldLabel } from "./ui/field";
4
+ import { Field, FieldLabel } from "terrazzo/ui";
5
5
  import {
6
6
  Pagination as PaginationRoot,
7
7
  PaginationContent,
8
8
  PaginationItem,
9
9
  PaginationNext,
10
10
  PaginationPrevious,
11
- } from "./ui/pagination";
11
+ } from "terrazzo/ui";
12
12
  import {
13
13
  Select,
14
14
  SelectContent,
@@ -16,7 +16,7 @@ import {
16
16
  SelectItem,
17
17
  SelectTrigger,
18
18
  SelectValue,
19
- } from "./ui/select";
19
+ } from "terrazzo/ui";
20
20
 
21
21
  export function Pagination({ currentPage, totalPages, totalCount, perPage, nextPagePath, prevPagePath }) {
22
22
  const { remote, pageKey } = useContext(NavigationContext);
@@ -1,7 +1,7 @@
1
1
  import React, { useRef, useContext } from "react";
2
2
  import { NavigationContext } from "@thoughtbot/superglue";
3
3
 
4
- import { Input } from "./ui/input";
4
+ import { Input } from "terrazzo/ui";
5
5
  import { Search } from "lucide-react";
6
6
 
7
7
  export function SearchBar({ searchTerm, searchPath }) {
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
3
3
 
4
- import { TableHead } from "./ui/table";
4
+ import { TableHead } from "terrazzo/ui";
5
5
 
6
6
  export function SortableHeader({ label, sortable, sortUrl, sortDirection }) {
7
7
  if (!sortable) {
@@ -11,7 +11,7 @@ import {
11
11
  SidebarMenu,
12
12
  SidebarMenuButton,
13
13
  SidebarMenuItem,
14
- } from "./ui/sidebar";
14
+ } from "terrazzo/ui";
15
15
 
16
16
  export function AppSidebar({
17
17
  navigation,
@@ -27,7 +27,7 @@ export function AppSidebar({
27
27
  asChild
28
28
  className="data-[slot=sidebar-menu-button]:!p-1.5">
29
29
 
30
- <a href={navigation[0]?.path ?? "#"} data-sg-visit>
30
+ <a href={navigation[0]?.items?.[0]?.path ?? navigation[0]?.path ?? "#"} data-sg-visit>
31
31
  <LayoutDashboardIcon className="h-5 w-5" />
32
32
  <span className="text-base font-semibold">{title}</span>
33
33
  </a>
@@ -36,26 +36,27 @@ export function AppSidebar({
36
36
  </SidebarMenu>
37
37
  </SidebarHeader>
38
38
  <SidebarContent>
39
- <SidebarGroup>
40
- <SidebarGroupLabel>Resources</SidebarGroupLabel>
41
- <SidebarGroupContent>
42
- <SidebarMenu>
43
- {navigation.map((item) =>
44
- <SidebarMenuItem key={item.path}>
45
- <SidebarMenuButton
46
- asChild
47
- isActive={item.active}
48
- tooltip={item.label}>
49
-
50
- <a href={item.path} data-sg-visit>
51
- <span>{item.label}</span>
52
- </a>
53
- </SidebarMenuButton>
54
- </SidebarMenuItem>
55
- )}
56
- </SidebarMenu>
57
- </SidebarGroupContent>
58
- </SidebarGroup>
39
+ {navigation.map((group) =>
40
+ <SidebarGroup key={group.label}>
41
+ <SidebarGroupLabel>{group.label}</SidebarGroupLabel>
42
+ <SidebarGroupContent>
43
+ <SidebarMenu>
44
+ {group.items.map((item) =>
45
+ <SidebarMenuItem key={item.path}>
46
+ <SidebarMenuButton
47
+ asChild
48
+ isActive={item.active}
49
+ tooltip={item.label}>
50
+ <a href={item.path} data-sg-visit>
51
+ <span>{item.label}</span>
52
+ </a>
53
+ </SidebarMenuButton>
54
+ </SidebarMenuItem>
55
+ )}
56
+ </SidebarMenu>
57
+ </SidebarGroupContent>
58
+ </SidebarGroup>
59
+ )}
59
60
  </SidebarContent>
60
61
  </Sidebar>);
61
62
 
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { Separator } from "./ui/separator";
3
- import { SidebarTrigger } from "./ui/sidebar";
2
+ import { Separator } from "terrazzo/ui";
3
+ import { SidebarTrigger } from "terrazzo/ui";
4
4
 
5
5
  export function SiteHeader({ title, actions }) {
6
6
  return (
@@ -97,7 +97,7 @@ export {
97
97
  SelectScrollDownButton,
98
98
  } from "./select";
99
99
  export {
100
- Pagination as UiPagination,
100
+ Pagination,
101
101
  PaginationContent,
102
102
  PaginationItem,
103
103
  PaginationLink,
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Label } from "../../components/ui/label";
3
+ import { Label } from "terrazzo/ui";
4
4
 
5
5
  export function FormField({ value, label, input, options, required }) {
6
6
  const resourceOptions = options?.resourceOptions ?? [];
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Label } from "../../components/ui/label";
3
+ import { Label } from "terrazzo/ui";
4
4
 
5
5
  export function FormField({ value, label, input, required }) {
6
6
  return (
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Badge } from "../../components/ui/badge";
3
+ import { Badge } from "terrazzo/ui";
4
4
 
5
5
  export function IndexField({ value }) {
6
6
  if (value == null) return <span className="text-muted-foreground">-</span>;
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useRef, useCallback } from "react";
2
2
  import { X, Check } from "lucide-react";
3
3
 
4
- import { Label } from "../../components/ui/label";
5
- import { Badge } from "../../components/ui/badge";
6
- import { Popover, PopoverTrigger, PopoverContent } from "../../components/ui/popover";
4
+ import { Label } from "terrazzo/ui";
5
+ import { Badge } from "terrazzo/ui";
6
+ import { Popover, PopoverTrigger, PopoverContent } from "terrazzo/ui";
7
7
  import { cn } from "terrazzo";
8
8
 
9
9
  export function FormField({ value, label, input, options, required }) {
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Badge } from "../../components/ui/badge";
3
+ import { Badge } from "terrazzo/ui";
4
4
 
5
5
  export function IndexField({ value }) {
6
6
  const count = typeof value === "number" ? value : 0;
@@ -1,4 +1,5 @@
1
- import React, { useState } from "react";
1
+ import React, { useState, useContext } from "react";
2
+ import { NavigationContext } from "@thoughtbot/superglue";
2
3
 
3
4
  import {
4
5
  Table,
@@ -7,9 +8,9 @@ import {
7
8
  TableRow,
8
9
  TableHead,
9
10
  TableCell,
10
- } from "../../components/ui/table";
11
- import { Badge } from "../../components/ui/badge";
12
- import { Button } from "../../components/ui/button";
11
+ } from "terrazzo/ui";
12
+ import { Badge } from "terrazzo/ui";
13
+ import { Button } from "terrazzo/ui";
13
14
  import { FieldRenderer } from "../FieldRenderer";
14
15
 
15
16
  export function ShowField({ value, itemShowPaths }) {
@@ -17,6 +18,7 @@ export function ShowField({ value, itemShowPaths }) {
17
18
 
18
19
  const { items, headers, total, initialLimit } = value;
19
20
  const [expanded, setExpanded] = useState(false);
21
+ const { visit } = useContext(NavigationContext);
20
22
 
21
23
  if (!items || items.length === 0) {
22
24
  return <span className="text-muted-foreground">None</span>;
@@ -26,6 +28,13 @@ export function ShowField({ value, itemShowPaths }) {
26
28
  const hasMore = initialLimit && initialLimit > 0 && total > initialLimit;
27
29
  const visibleItems = expanded || !hasMore ? items : items.slice(0, initialLimit);
28
30
 
31
+ const handleRowClick = (e, showPath) => {
32
+ if (!showPath) return;
33
+ if (e.target.closest("a, button, form")) return;
34
+ if (window.getSelection().toString()) return;
35
+ visit(showPath, {});
36
+ };
37
+
29
38
  // Table mode: collection_attributes specified
30
39
  if (headers) {
31
40
  return (
@@ -43,7 +52,10 @@ export function ShowField({ value, itemShowPaths }) {
43
52
  {visibleItems.map((item) => {
44
53
  const showPath = pathFor(item.id);
45
54
  return (
46
- <TableRow key={item.id}>
55
+ <TableRow
56
+ key={item.id}
57
+ className={showPath ? "cursor-pointer" : ""}
58
+ onClick={(e) => handleRowClick(e, showPath)}>
47
59
  {item.columns.map((col, colIndex) =>
48
60
  <TableCell key={col.attribute}>
49
61
  {showPath && colIndex === 0 ? (
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Label } from "../../components/ui/label";
3
+ import { Label } from "terrazzo/ui";
4
4
 
5
5
  export function FormField({ value, label, input, options, required }) {
6
6
  const resourceOptions = options?.resourceOptions ?? [];
@@ -1,8 +1,8 @@
1
1
  import React, { useState, useRef, useEffect } from "react";
2
2
 
3
- import { Input } from "../../components/ui/input";
4
- import { Button } from "../../components/ui/button";
5
- import { Label } from "../../components/ui/label";
3
+ import { Input } from "terrazzo/ui";
4
+ import { Button } from "terrazzo/ui";
5
+ import { Label } from "terrazzo/ui";
6
6
 
7
7
  export function FormField({ value, label, input, options, required }) {
8
8
  const initialPairs = () => {
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Badge } from "../../components/ui/badge";
3
+ import { Badge } from "terrazzo/ui";
4
4
 
5
5
  export function IndexField({ value }) {
6
6
  if (value == null || value === "") {
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Badge } from "../../components/ui/badge";
3
+ import { Badge } from "terrazzo/ui";
4
4
 
5
5
  export function ShowField({ value }) {
6
6
  if (value == null || Object.keys(value).length === 0) {
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react";
2
2
 
3
- import { Label } from "../../components/ui/label";
3
+ import { Label } from "terrazzo/ui";
4
4
 
5
5
  export function FormField({ value, label, input, options, required }) {
6
6
  const groupedOptions = options?.groupedOptions ?? {};
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { Textarea } from "../../components/ui/textarea";
4
- import { Label } from "../../components/ui/label";
3
+ import { Textarea } from "terrazzo/ui";
4
+ import { Label } from "terrazzo/ui";
5
5
 
6
6
  export function FormField({ value, label, input, required }) {
7
7
  return (
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Label } from "../../components/ui/label";
3
+ import { Label } from "terrazzo/ui";
4
4
 
5
5
  export function FormField({ value, label, input, options, required }) {
6
6
  const selectableOptions = options?.selectableOptions ?? [];
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { Badge } from "../../components/ui/badge";
3
+ import { Badge } from "terrazzo/ui";
4
4
 
5
5
  export function IndexField({ value }) {
6
6
  if (value == null) return <span className="text-muted-foreground">-</span>;
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { Input } from "../../components/ui/input";
4
- import { Label } from "../../components/ui/label";
3
+ import { Input } from "terrazzo/ui";
4
+ import { Label } from "terrazzo/ui";
5
5
 
6
6
  export function TextInputFormField({ type = "text", value, label, input, required, defaultValue, ...inputProps }) {
7
7
  const resolvedDefault = defaultValue !== undefined ? defaultValue : String(value ?? "");
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { Textarea } from "../../components/ui/textarea";
4
- import { Label } from "../../components/ui/label";
3
+ import { Textarea } from "terrazzo/ui";
4
+ import { Label } from "terrazzo/ui";
5
5
 
6
6
  export function FormField({ value, label, input, required }) {
7
7
  return (
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { FieldRenderer } from "../fields/FieldRenderer";
4
- import { Button } from "../components/ui/button";
3
+ import { FieldRenderer } from "terrazzo/fields";
4
+ import { Button } from "terrazzo/ui";
5
5
 
6
6
  export function AdminForm({ form, errors }) {
7
7
  const { props: formProps, extras, fieldGroups, fields } = form;
@@ -0,0 +1,5 @@
1
+ json.array! Terrazzo::Namespace.new(namespace).resources_with_index_route do |nav_resource|
2
+ json.label nav_resource.resource_name.humanize.pluralize
3
+ json.path url_for(controller: "/#{nav_resource.controller_path}", action: :index, only_path: true)
4
+ json.active nav_resource.controller_path == controller_path
5
+ end
@@ -1,10 +1,9 @@
1
1
  import React from "react";
2
2
  import { useContent } from "@thoughtbot/superglue";
3
3
 
4
- import { Layout } from "../components/Layout";
4
+ import { Layout } from "terrazzo/components";
5
5
  import { AdminForm } from "./_form";
6
- import { Button } from "../components/ui/button";
7
- import { Card, CardContent } from "../components/ui/card";
6
+ import { Button, Card, CardContent } from "terrazzo/ui";
8
7
 
9
8
  export default function AdminEdit() {
10
9
  const {
@@ -1,20 +1,9 @@
1
1
  import React, { useContext } from "react";
2
2
  import { useContent, NavigationContext } from "@thoughtbot/superglue";
3
3
 
4
- import { Layout } from "../components/Layout";
5
- import { SearchBar } from "../components/SearchBar";
6
- import { Pagination } from "../components/Pagination";
7
- import { SortableHeader } from "../components/SortableHeader";
8
- import { FieldRenderer } from "../fields/FieldRenderer";
9
- import { Button } from "../components/ui/button";
10
- import {
11
- Table,
12
- TableHeader,
13
- TableBody,
14
- TableRow,
15
- TableHead,
16
- TableCell,
17
- } from "../components/ui/table";
4
+ import { Layout, SearchBar, Pagination, SortableHeader } from "terrazzo/components";
5
+ import { FieldRenderer } from "terrazzo/fields";
6
+ import { Button, Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "terrazzo/ui";
18
7
 
19
8
  export default function AdminIndex() {
20
9
  const { visit } = useContext(NavigationContext);
@@ -1,10 +1,9 @@
1
1
  import React from "react";
2
2
  import { useContent } from "@thoughtbot/superglue";
3
3
 
4
- import { Layout } from "../components/Layout";
4
+ import { Layout } from "terrazzo/components";
5
5
  import { AdminForm } from "./_form";
6
- import { Button } from "../components/ui/button";
7
- import { Card, CardContent } from "../components/ui/card";
6
+ import { Button, Card, CardContent } from "terrazzo/ui";
8
7
 
9
8
  export default function AdminNew() {
10
9
  const {
@@ -1,10 +1,9 @@
1
1
  import React from "react";
2
2
  import { useContent } from "@thoughtbot/superglue";
3
3
 
4
- import { Layout } from "../components/Layout";
5
- import { FieldRenderer } from "../fields/FieldRenderer";
6
- import { Button } from "../components/ui/button";
7
- import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
4
+ import { Layout } from "terrazzo/components";
5
+ import { FieldRenderer } from "terrazzo/fields";
6
+ import { Button, Card, CardContent, CardHeader, CardTitle } from "terrazzo/ui";
8
7
 
9
8
  export default function AdminShow() {
10
9
  const {
@@ -8,38 +8,57 @@ module Terrazzo
8
8
  class_option :namespace, type: :string, default: "admin",
9
9
  desc: "Admin namespace"
10
10
 
11
- def copy_ui_components
12
- directory "components/ui", "app/views/#{namespace_name}/components/ui"
11
+ def create_fields_barrel
12
+ create_file "app/views/#{namespace_name}/fields/index.js", <<~JS
13
+ // Re-export all fields from the terrazzo package.
14
+ // To customize a field, run: rails g terrazzo:eject fields/<field_type>
15
+ export * from "terrazzo/fields";
16
+ JS
13
17
  end
14
18
 
15
- def copy_shared_components
16
- %w[
17
- Layout.jsx
18
- app-sidebar.jsx
19
- site-header.jsx
20
- FlashMessages.jsx
21
- SearchBar.jsx
22
- Pagination.jsx
23
- SortableHeader.jsx
24
- ].each do |file|
25
- copy_file "components/#{file}", "app/views/#{namespace_name}/components/#{file}"
26
- end
19
+ def create_components_barrel
20
+ create_file "app/views/#{namespace_name}/components/index.js", <<~JS
21
+ // Re-export all components from the terrazzo package.
22
+ // To customize, run: rails g terrazzo:eject components/<component_name>
23
+ export * from "terrazzo/components";
24
+ JS
27
25
  end
28
26
 
29
- def copy_page_templates
30
- {
31
- "pages/index.jsx" => "application/index.jsx",
32
- "pages/show.jsx" => "application/show.jsx",
33
- "pages/new.jsx" => "application/new.jsx",
34
- "pages/edit.jsx" => "application/edit.jsx",
35
- "pages/_form.jsx" => "application/_form.jsx",
36
- }.each do |src, dest|
37
- copy_file src, "app/views/#{namespace_name}/#{dest}"
38
- end
27
+ def create_ui_barrel
28
+ create_file "app/views/#{namespace_name}/components/ui/index.js", <<~JS
29
+ // Re-export all UI primitives from the terrazzo package.
30
+ // To customize, run: rails g terrazzo:eject ui/<component_name>
31
+ export * from "terrazzo/ui";
32
+ JS
33
+ end
34
+
35
+ def create_navigation_partial
36
+ create_file "app/views/#{namespace_name}/application/_navigation.json.props", <<~RUBY
37
+ resources = Terrazzo::Namespace.new(namespace).resources_with_index_route
38
+
39
+ json.array! [{ label: "Resources", resources: resources }] do |group|
40
+ json.label group[:label]
41
+ json.items do
42
+ json.array! group[:resources] do |r|
43
+ json.label r.resource_name.humanize.pluralize
44
+ json.path url_for(controller: "/\#{r.controller_path}", action: :index, only_path: true)
45
+ json.active r.controller_path == controller_path
46
+ end
47
+ end
48
+ end
49
+ RUBY
39
50
  end
40
51
 
41
- def copy_field_components
42
- directory "fields", "app/views/#{namespace_name}/fields"
52
+ def create_page_stubs
53
+ {
54
+ "index" => "AdminIndex",
55
+ "show" => "AdminShow",
56
+ "new" => "AdminNew",
57
+ "edit" => "AdminEdit",
58
+ }.each do |page, component|
59
+ create_file "app/views/#{namespace_name}/application/#{page}.jsx",
60
+ "export { #{component} as default } from \"terrazzo/pages\";\n"
61
+ end
43
62
  end
44
63
 
45
64
  private
@@ -44,7 +44,7 @@ module Terrazzo
44
44
  associated_class.all
45
45
  end
46
46
  pk = association_primary_key
47
- scope.map { |r| [display_name(r), r.public_send(pk)] }
47
+ scope.map { |r| [display_name(r), r.public_send(pk).to_s] }
48
48
  end
49
49
 
50
50
  def association_primary_key
@@ -4,10 +4,10 @@ module Terrazzo
4
4
  def serialize_value(mode)
5
5
  case mode
6
6
  when :form
7
- foreign_key_value
7
+ foreign_key_value&.to_s
8
8
  else
9
9
  return nil if data.nil?
10
- { id: data.id, display: display_name(data) }
10
+ { id: data.id.to_s, display: display_name(data) }
11
11
  end
12
12
  end
13
13
 
@@ -8,11 +8,11 @@ module Terrazzo
8
8
  when :index
9
9
  data.size
10
10
  when :form
11
- data.map(&:id)
11
+ data.map { |r| r.id.to_s }
12
12
  when :show
13
13
  serialize_show_value
14
14
  else
15
- data.map { |r| { id: r.id, display: display_name(r) } }
15
+ data.map { |r| { id: r.id.to_s, display: display_name(r) } }
16
16
  end
17
17
  end
18
18
 
@@ -44,19 +44,26 @@ module Terrazzo
44
44
  limit = options.fetch(:limit, 5)
45
45
  all_records = data.to_a
46
46
  total = all_records.size
47
- col_attrs = options[:collection_attributes]
47
+ col_attrs = options[:collection_attributes] || resolve_default_collection_attributes
48
48
 
49
49
  if col_attrs
50
50
  serialize_with_collection_attributes(all_records, col_attrs, total, limit)
51
51
  else
52
52
  {
53
- items: all_records.map { |r| { id: r.id, display: display_name(r) } },
53
+ items: all_records.map { |r| { id: r.id.to_s, display: display_name(r) } },
54
54
  total: total,
55
55
  initialLimit: limit
56
56
  }
57
57
  end
58
58
  end
59
59
 
60
+ def resolve_default_collection_attributes
61
+ dashboard_class = find_associated_dashboard
62
+ dashboard_class.new.collection_attributes
63
+ rescue NameError
64
+ nil
65
+ end
66
+
60
67
  def serialize_with_collection_attributes(records, col_attrs, total, limit)
61
68
  dashboard_class = find_associated_dashboard
62
69
 
@@ -73,7 +80,7 @@ module Terrazzo
73
80
  value: field.serialize_value(:index)
74
81
  }
75
82
  end
76
- { id: record.id, columns: columns }
83
+ { id: record.id.to_s, columns: columns }
77
84
  end
78
85
 
79
86
  {
@@ -4,10 +4,10 @@ module Terrazzo
4
4
  def serialize_value(mode)
5
5
  case mode
6
6
  when :form
7
- data&.id
7
+ data&.id&.to_s
8
8
  else
9
9
  return nil if data.nil?
10
- { id: data.id, display: display_name(data) }
10
+ { id: data.id.to_s, display: display_name(data) }
11
11
  end
12
12
  end
13
13
 
@@ -5,10 +5,10 @@ module Terrazzo
5
5
  case mode
6
6
  when :form
7
7
  return nil if data.nil?
8
- { type: data.class.name, id: data.id }
8
+ { type: data.class.name, id: data.id.to_s }
9
9
  else
10
10
  return nil if data.nil?
11
- { id: data.id, display: display_name(data), type: data.class.name }
11
+ { id: data.id.to_s, display: display_name(data), type: data.class.name }
12
12
  end
13
13
  end
14
14
 
@@ -17,7 +17,7 @@ module Terrazzo
17
17
  classes = options[:classes] || []
18
18
  opts[:groupedOptions] = classes.each_with_object({}) do |klass, hash|
19
19
  klass = klass.constantize if klass.is_a?(::String)
20
- hash[klass.name] = klass.all.map { |r| [display_name(r), r.id] }
20
+ hash[klass.name] = klass.all.map { |r| [display_name(r), r.id.to_s] }
21
21
  end
22
22
  opts
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module Terrazzo
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/terrazzo.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.email = []
8
8
  spec.summary = "A Rails admin framework powered by Superglue and React"
9
9
  spec.description = "Drop-in admin panel for Rails apps using the Administrate dashboard DSL with a React SPA frontend powered by Superglue."
10
- spec.homepage = "https://github.com/wengzilla/terrazzo"
10
+ spec.homepage = "https://github.com/gohypelab/terrazzo"
11
11
  spec.license = "MIT"
12
12
  spec.required_ruby_version = ">= 3.1"
13
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terrazzo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Terrazzo Contributors
@@ -117,6 +117,7 @@ files:
117
117
  - LICENSE
118
118
  - Rakefile
119
119
  - app/controllers/terrazzo/application_controller.rb
120
+ - app/views/terrazzo/application/_navigation.json.props
120
121
  - app/views/terrazzo/application/edit.json.props
121
122
  - app/views/terrazzo/application/index.json.props
122
123
  - app/views/terrazzo/application/new.json.props
@@ -126,6 +127,7 @@ files:
126
127
  - lib/generators/terrazzo/dashboard/dashboard_generator.rb
127
128
  - lib/generators/terrazzo/dashboard/templates/controller.rb.erb
128
129
  - lib/generators/terrazzo/dashboard/templates/dashboard.rb.erb
130
+ - lib/generators/terrazzo/eject/eject_generator.rb
129
131
  - lib/generators/terrazzo/field/field_generator.rb
130
132
  - lib/generators/terrazzo/field/templates/FormField.jsx.erb
131
133
  - lib/generators/terrazzo/field/templates/IndexField.jsx.erb
@@ -134,8 +136,8 @@ files:
134
136
  - lib/generators/terrazzo/install/install_generator.rb
135
137
  - lib/generators/terrazzo/install/templates/admin.css
136
138
  - lib/generators/terrazzo/install/templates/application.js.erb
137
- - lib/generators/terrazzo/install/templates/application.json.props
138
139
  - lib/generators/terrazzo/install/templates/application.json.props.erb
140
+ - lib/generators/terrazzo/install/templates/application.json.props.tt
139
141
  - lib/generators/terrazzo/install/templates/application_controller.rb.erb
140
142
  - lib/generators/terrazzo/install/templates/application_visit.js.erb
141
143
  - lib/generators/terrazzo/install/templates/flash_slice.js.erb
@@ -231,6 +233,7 @@ files:
231
233
  - lib/generators/terrazzo/views/templates/fields/url/IndexField.jsx
232
234
  - lib/generators/terrazzo/views/templates/fields/url/ShowField.jsx
233
235
  - lib/generators/terrazzo/views/templates/pages/_form.jsx
236
+ - lib/generators/terrazzo/views/templates/pages/_navigation.json.props
234
237
  - lib/generators/terrazzo/views/templates/pages/edit.jsx
235
238
  - lib/generators/terrazzo/views/templates/pages/index.jsx
236
239
  - lib/generators/terrazzo/views/templates/pages/new.jsx
@@ -275,7 +278,7 @@ files:
275
278
  - lib/terrazzo/uses_superglue.rb
276
279
  - lib/terrazzo/version.rb
277
280
  - terrazzo.gemspec
278
- homepage: https://github.com/wengzilla/terrazzo
281
+ homepage: https://github.com/gohypelab/terrazzo
279
282
  licenses:
280
283
  - MIT
281
284
  metadata: {}