terrazzo 0.2.1 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0668700d10e6547d3366e74348a33674733828a834d3be9dd02ddd7effab6bd8'
4
- data.tar.gz: d85d82e780f8820d1b306ac5f63c3d1a96802da1f56f301e11b8040903372601
3
+ metadata.gz: 8912409821769ce6f2303842822a9d33696bef4a24c9fdc9974411a99dccc7b0
4
+ data.tar.gz: a171f982161da734439ac15c77cafa831ace5f8f9b8f87b43d93aa3107cf67d1
5
5
  SHA512:
6
- metadata.gz: 84bff5afcc8deb6b423f7112593c318b80716c384d7e1140d596e50b816de4e8c651bb76d9cfa4653b5154ec1c28a7185052928f136fcdafa044ef3d8762f4c4
7
- data.tar.gz: eaec2494d92552710c555f3f45db30688e29ca4a20af4d29c488aa8d15f62efaa42d7ae8e399c95d5c3cb291bb36f3001f9e96e5e4c47d02ae3009490b377f3c
6
+ metadata.gz: 1394dab4447f66fbda0801d3b60a74ddd8f0325b6927562c4f8cf7b33212491b029efc78b12d5d8eb887496fed9261b8abb3b75f4e5f2148d74a80061951643a
7
+ data.tar.gz: f16c4e076d8f693b789622073df1fd9ccb5bedf36afb4eaa1032c4da09e8d87cc69b5a4929c8dcea28d0228c2ef53fc7476e71c8e12eee1e98a8b057982bad4a
@@ -42,7 +42,10 @@ 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
45
+
46
+ if field.class.associative? && field.data.present?
47
+ json.showPath polymorphic_path([namespace, field.data]) rescue nil
48
+ end
46
49
  end
47
50
  end
48
51
  end
@@ -1,15 +1,28 @@
1
1
  json.pageTitle @page.page_title
2
2
 
3
+ show_field_json = ->(json, field) do
4
+ json.attribute field.attribute.to_s
5
+ json.label field.attribute.to_s.humanize
6
+ json.fieldType field.field_type
7
+ json.value field.serialize_value(:show)
8
+
9
+ if field.class.associative? && field.data.present?
10
+ if field.is_a?(Terrazzo::Field::HasMany)
11
+ json.itemShowPaths(field.data.each_with_object({}) do |record, paths|
12
+ paths[record.id.to_s] = polymorphic_path([namespace, record]) rescue nil
13
+ end)
14
+ else
15
+ json.showPath polymorphic_path([namespace, field.data]) rescue nil
16
+ end
17
+ end
18
+ end
19
+
3
20
  json.attributeGroups do
4
21
  json.array! @page.grouped_attributes do |group|
5
22
  json.name group[:name]
6
23
  json.attributes do
7
24
  json.array! group[:fields] do |field|
8
- json.attribute field.attribute.to_s
9
- json.label field.attribute.to_s.humanize
10
- json.fieldType field.field_type
11
- json.value field.serialize_value(:show)
12
- json.options field.serializable_options
25
+ show_field_json.call(json, field)
13
26
  end
14
27
  end
15
28
  end
@@ -17,11 +30,7 @@ end
17
30
 
18
31
  json.attributes do
19
32
  json.array! @page.attributes do |field|
20
- json.attribute field.attribute.to_s
21
- json.label field.attribute.to_s.humanize
22
- json.fieldType field.field_type
23
- json.value field.serialize_value(:show)
24
- json.options field.serializable_options
33
+ show_field_json.call(json, field)
25
34
  end
26
35
  end
27
36
 
@@ -1,4 +1,5 @@
1
- import React from "react";
1
+ import React, { useState, useContext } from "react";
2
+ import { NavigationContext } from "@thoughtbot/superglue";
2
3
 
3
4
  import {
4
5
  Table,
@@ -8,17 +9,32 @@ import {
8
9
  TableHead,
9
10
  TableCell,
10
11
  } from "../../components/ui/table";
12
+ import { Badge } from "../../components/ui/badge";
13
+ import { Button } from "../../components/ui/button";
11
14
  import { FieldRenderer } from "../FieldRenderer";
12
15
 
13
- export function ShowField({ value }) {
16
+ export function ShowField({ value, itemShowPaths }) {
14
17
  if (!value) return <span className="text-muted-foreground">None</span>;
15
18
 
16
- const { items, headers, total, hasMore } = value;
19
+ const { items, headers, total, initialLimit } = value;
20
+ const [expanded, setExpanded] = useState(false);
21
+ const { visit } = useContext(NavigationContext);
17
22
 
18
23
  if (!items || items.length === 0) {
19
24
  return <span className="text-muted-foreground">None</span>;
20
25
  }
21
26
 
27
+ const pathFor = (id) => itemShowPaths?.[String(id)];
28
+ const hasMore = initialLimit && initialLimit > 0 && total > initialLimit;
29
+ const visibleItems = expanded || !hasMore ? items : items.slice(0, initialLimit);
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
+
22
38
  // Table mode: collection_attributes specified
23
39
  if (headers) {
24
40
  return (
@@ -33,22 +49,38 @@ export function ShowField({ value }) {
33
49
  </TableRow>
34
50
  </TableHeader>
35
51
  <TableBody>
36
- {items.map((item) =>
37
- <TableRow key={item.id}>
38
- {item.columns.map((col) =>
39
- <TableCell key={col.attribute}>
40
- <FieldRenderer mode="index" {...col} />
41
- </TableCell>
42
- )}
43
- </TableRow>
44
- )}
52
+ {visibleItems.map((item) => {
53
+ const showPath = pathFor(item.id);
54
+ return (
55
+ <TableRow
56
+ key={item.id}
57
+ className={showPath ? "cursor-pointer" : ""}
58
+ onClick={(e) => handleRowClick(e, showPath)}>
59
+ {item.columns.map((col, colIndex) =>
60
+ <TableCell key={col.attribute}>
61
+ {showPath && colIndex === 0 ? (
62
+ <a href={showPath} data-sg-visit className="hover:underline">
63
+ <FieldRenderer mode="index" {...col} />
64
+ </a>
65
+ ) : (
66
+ <FieldRenderer mode="index" {...col} />
67
+ )}
68
+ </TableCell>
69
+ )}
70
+ </TableRow>
71
+ );
72
+ })}
45
73
  </TableBody>
46
74
  </Table>
47
75
  </div>
48
76
  {hasMore && (
49
- <p className="text-sm text-muted-foreground mt-2">
50
- Showing {items.length} of {total}
51
- </p>
77
+ <Button
78
+ variant="link"
79
+ size="sm"
80
+ className="mt-2 px-0"
81
+ onClick={() => setExpanded(!expanded)}>
82
+ {expanded ? "Show less" : `Show ${total - initialLimit} more`}
83
+ </Button>
52
84
  )}
53
85
  </div>
54
86
  );
@@ -57,16 +89,27 @@ export function ShowField({ value }) {
57
89
  // Simple list mode
58
90
  return (
59
91
  <div>
60
- <ul className="list-disc pl-5 space-y-1">
61
- {items.map((item) =>
62
- <li key={item.id}>{item.display}</li>
92
+ <div className="flex flex-wrap items-center gap-1.5">
93
+ {visibleItems.map((item) => {
94
+ const showPath = pathFor(item.id);
95
+ return showPath ? (
96
+ <a key={item.id} href={showPath} data-sg-visit>
97
+ <Badge variant="secondary" className="cursor-pointer">{item.display}</Badge>
98
+ </a>
99
+ ) : (
100
+ <Badge key={item.id} variant="secondary">{item.display}</Badge>
101
+ );
102
+ })}
103
+ {hasMore && (
104
+ <Button
105
+ variant="link"
106
+ size="sm"
107
+ className="px-0"
108
+ onClick={() => setExpanded(!expanded)}>
109
+ {expanded ? "Show less" : `Show ${total - initialLimit} more`}
110
+ </Button>
63
111
  )}
64
- </ul>
65
- {hasMore && (
66
- <p className="text-sm text-muted-foreground mt-2">
67
- Showing {items.length} of {total}
68
- </p>
69
- )}
112
+ </div>
70
113
  </div>
71
114
  );
72
115
  }
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { useContent } from "@thoughtbot/superglue";
1
+ import React, { useContext } from "react";
2
+ import { useContent, NavigationContext } from "@thoughtbot/superglue";
3
3
 
4
4
  import { Layout } from "../components/Layout";
5
5
  import { SearchBar } from "../components/SearchBar";
@@ -17,6 +17,7 @@ import {
17
17
  } from "../components/ui/table";
18
18
 
19
19
  export default function AdminIndex() {
20
+ const { visit } = useContext(NavigationContext);
20
21
  const {
21
22
  table,
22
23
  searchBar,
@@ -27,6 +28,13 @@ export default function AdminIndex() {
27
28
  singularResourceName
28
29
  } = useContent();
29
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
+
30
38
  return (
31
39
  <Layout
32
40
  navigation={navigation}
@@ -53,10 +61,19 @@ export default function AdminIndex() {
53
61
  </TableHeader>
54
62
  <TableBody>
55
63
  {table.rows.map((row) =>
56
- <TableRow key={row.id}>
64
+ <TableRow
65
+ key={row.id}
66
+ className={row.showPath ? "cursor-pointer" : ""}
67
+ onClick={(e) => handleRowClick(e, row.showPath)}>
57
68
  {row.cells.map((cell) =>
58
69
  <TableCell key={cell.attribute}>
59
- <FieldRenderer mode="index" {...cell} />
70
+ {cell.showPath ? (
71
+ <a href={cell.showPath} data-sg-visit className="hover:underline">
72
+ <FieldRenderer mode="index" {...cell} />
73
+ </a>
74
+ ) : (
75
+ <FieldRenderer mode="index" {...cell} />
76
+ )}
60
77
  </TableCell>
61
78
  )}
62
79
  <TableCell>
@@ -69,7 +69,13 @@ export default function AdminShow() {
69
69
  {attr.label}
70
70
  </dt>
71
71
  <dd className="col-span-2 text-sm">
72
- <FieldRenderer mode="show" {...attr} />
72
+ {attr.showPath ? (
73
+ <a href={attr.showPath} data-sg-visit className="hover:underline">
74
+ <FieldRenderer mode="show" {...attr} />
75
+ </a>
76
+ ) : (
77
+ <FieldRenderer mode="show" {...attr} />
78
+ )}
73
79
  </dd>
74
80
  </div>
75
81
  )}
@@ -42,26 +42,28 @@ module Terrazzo
42
42
 
43
43
  def serialize_show_value
44
44
  limit = options.fetch(:limit, 5)
45
- total = data.size
46
- col_attrs = options[:collection_attributes]
47
-
48
- records = if limit && limit > 0
49
- data.respond_to?(:limit) ? data.limit(limit) : data.first(limit)
50
- else
51
- data
52
- end
45
+ all_records = data.to_a
46
+ total = all_records.size
47
+ col_attrs = options[:collection_attributes] || resolve_default_collection_attributes
53
48
 
54
49
  if col_attrs
55
- serialize_with_collection_attributes(records, col_attrs, total, limit)
50
+ serialize_with_collection_attributes(all_records, col_attrs, total, limit)
56
51
  else
57
52
  {
58
- items: records.map { |r| { id: r.id, display: display_name(r) } },
53
+ items: all_records.map { |r| { id: r.id, display: display_name(r) } },
59
54
  total: total,
60
- hasMore: limit && limit > 0 ? total > limit : false
55
+ initialLimit: limit
61
56
  }
62
57
  end
63
58
  end
64
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
+
65
67
  def serialize_with_collection_attributes(records, col_attrs, total, limit)
66
68
  dashboard_class = find_associated_dashboard
67
69
 
@@ -85,7 +87,7 @@ module Terrazzo
85
87
  headers: headers,
86
88
  items: items,
87
89
  total: total,
88
- hasMore: limit && limit > 0 ? total > limit : false
90
+ initialLimit: limit
89
91
  }
90
92
  end
91
93
 
@@ -1,3 +1,3 @@
1
1
  module Terrazzo
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.3"
3
3
  end
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.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Terrazzo Contributors