yummy-guide-generic-administrate 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b78001f457aaa653797c44c84f1d169f7e870932e5b7ea43ae1d3994ecab9747
4
- data.tar.gz: 437ffe942299cbda22d3eca1e72ecc625069880862848b94d3da3520cb3ecf12
3
+ metadata.gz: 1094484e89c98a0f68d56b6536786231e367755b9c3d260340180c688cff1b89
4
+ data.tar.gz: 46974073ea46232ca7482666c6ca8d1cbfedbee943b95783e0d7ad4d5c56ef77
5
5
  SHA512:
6
- metadata.gz: 8a8b10627365f4b549f43701974d3e8ab0066d6a656bdab19fbe310dce3ed258b1b80e58db16ce72219bda72030f9269fd71a752324a016cc44426464d64d0c4
7
- data.tar.gz: 589d405d80686573b6fd8efd2a45795861e010203b703f85e1a0c142dec044c8cf603189480f1adfd581c8fc24c61690a56335c1b487d3be869322c7644bdc91
6
+ metadata.gz: 56fe590e3c211b91e7235a98e389700d8eed1fd2c636bf6a654f4f84696429ebf4c4fb81a7cddd752f20c1bbad21bb4a463de23b535a47c84be201daccbfb6a6
7
+ data.tar.gz: af71f0ebb0e6f825f59ead48d3d0b4292313740aa58ea244b875f7cff2c3957a4d12329000fc5e95f8c2e3fe065e4fecde0e8e75aa19ca1ca3532410a15bdf00
data/README.md CHANGED
@@ -43,9 +43,12 @@ bundle install
43
43
  - datetime フィルターや checkbox group の組み立てを補助する helper
44
44
  - 共通 partial / assets
45
45
  - collection partial
46
+ - fixed table header partial
46
47
  - filter form partial
48
+ - `clipboards.js`
47
49
  - `filter_form.js`
48
50
  - `sticky_left_columns.js`
51
+ - `sticky_table_headers.js`
49
52
  - `components.css`
50
53
  - 共通 field
51
54
  - `YummyGuide::Administrate::Fields::JsonPrettyField`
@@ -113,6 +116,104 @@ engine の共通 partial に委譲します。
113
116
  collection_field_name: collection_field_name %>
114
117
  ```
115
118
 
119
+ ### 固定ヘッダーの設定
120
+
121
+ #### 1. 最小構成
122
+
123
+ gem 付属の collection partial をそのまま使う場合、table wrapper と table 本体に
124
+ 必要な `data-*` 属性はすでに入っています。そのため、JS / CSS を読み込めば固定
125
+ ヘッダーは自動で有効になります。
126
+
127
+ 内部的には以下のような構造になります。
128
+
129
+ ```erb
130
+ <div class="scroll-table" data-fixed-header-scroll>
131
+ <table
132
+ aria-labelledby="<%= table_title %>"
133
+ data-fixed-columns-count="<%= yummy_guide_administrate_collection_table_fixed_columns_count(page: page, collection_presenter: collection_presenter) %>"
134
+ data-fixed-header-source
135
+ >
136
+ ...
137
+ </table>
138
+ </div>
139
+ ```
140
+
141
+ #### 2. ヘッダー位置を明示したい場合
142
+
143
+ 固定ヘッダーの表示位置をページ上部の特定箇所に合わせたい場合は、
144
+ `data-fixed-table-header` を持つ slot を `.main-content` 配下に置きます。
145
+ gem には専用 partial があります。
146
+
147
+ ```erb
148
+ <header class="main-content__header">
149
+ <h1 id="page-title">Articles</h1>
150
+ <%= render "yummy_guide/administrate/administrate/application/fixed_table_header" %>
151
+ </header>
152
+
153
+ <section class="main-content__body">
154
+ <%= render "yummy_guide/administrate/administrate/application/collection",
155
+ collection_presenter: collection_presenter,
156
+ page: page,
157
+ resources: resources,
158
+ table_title: "page-title",
159
+ namespace: :admin,
160
+ resource_class: resource_class,
161
+ collection_field_name: resource_name %>
162
+ </section>
163
+ ```
164
+
165
+ `fixed_table_header` partial 自体は以下の 1 行です。
166
+
167
+ ```erb
168
+ <div data-fixed-table-header hidden></div>
169
+ ```
170
+
171
+ この slot を置かない場合でも、JS が table の直前に自動生成します。配置を制御
172
+ したいときだけ明示してください。
173
+
174
+ #### 3. 自前の table partial を使う場合
175
+
176
+ 独自の collection partial を書く場合は、少なくとも以下を満たしてください。
177
+
178
+ - 横スクロール wrapper に `data-fixed-header-scroll` を付ける
179
+ - table に `data-fixed-header-source` を付ける
180
+ - table に `data-fixed-columns-count` を付ける
181
+ - header の `aria-labelledby` がページタイトルと対応している
182
+
183
+ ```erb
184
+ <%= render "yummy_guide/administrate/administrate/application/fixed_table_header" %>
185
+
186
+ <div class="scroll-table" data-fixed-header-scroll>
187
+ <table
188
+ aria-labelledby="page-title"
189
+ data-fixed-header-source
190
+ data-fixed-columns-count="<%= yummy_guide_administrate_collection_table_fixed_columns_count(page: page, collection_presenter: collection_presenter) %>"
191
+ >
192
+ <thead>
193
+ <tr>
194
+ <th>ID</th>
195
+ <th>Name</th>
196
+ <th class="sticky actions-column">Actions</th>
197
+ </tr>
198
+ </thead>
199
+ <tbody>
200
+ <% resources.each do |resource| %>
201
+ <tr>
202
+ <td><%= resource.id %></td>
203
+ <td><%= resource.name %></td>
204
+ <td class="sticky actions-column">
205
+ <%= link_to "Show", [:admin, resource] %>
206
+ </td>
207
+ </tr>
208
+ <% end %>
209
+ </tbody>
210
+ </table>
211
+ </div>
212
+ ```
213
+
214
+ `sticky actions-column` を action 列に付けると、右端列も固定できます。
215
+ 左端の固定列数は dashboard 側の `INDEX_FIXED_COLUMNS_COUNT` で制御します。
216
+
116
217
  ### Datetime filter
117
218
 
118
219
  filter form の枠と datetime 入力 partial を組み合わせて利用できます。
@@ -157,8 +258,10 @@ end
157
258
  この engine の asset はホストアプリ側で明示的に読み込んでください。
158
259
 
159
260
  ```js
261
+ //= require yummy_guide_administrate/clipboards
160
262
  //= require yummy_guide_administrate/filter_form
161
263
  //= require yummy_guide_administrate/sticky_left_columns
264
+ //= require yummy_guide_administrate/sticky_table_headers
162
265
  ```
163
266
 
164
267
  ```scss
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
2
+ <rect x="5.25" y="2.25" width="8.5" height="10.5" rx="1.5" stroke="currentColor" stroke-width="1.25"/>
3
+ <path d="M3.75 5.25H3A1.75 1.75 0 0 0 1.25 7v6A1.75 1.75 0 0 0 3 14.75h5A1.75 1.75 0 0 0 9.75 13v-.75" stroke="currentColor" stroke-width="1.25" stroke-linecap="round"/>
4
+ </svg>
@@ -0,0 +1,81 @@
1
+ (function() {
2
+ function dispatchCopyEvent(name, detail) {
3
+ document.dispatchEvent(new CustomEvent(name, { detail: detail }));
4
+ }
5
+
6
+ function showCopiedFeedback(trigger) {
7
+ var container = trigger.closest(".admin-copy-cell");
8
+ if (!container) return;
9
+
10
+ var feedback = container.querySelector("[data-role='copy-feedback']");
11
+ if (!feedback) return;
12
+
13
+ feedback.textContent = "Copied";
14
+ container.classList.add("is-copied");
15
+
16
+ if (container.copyFeedbackTimer) {
17
+ clearTimeout(container.copyFeedbackTimer);
18
+ }
19
+
20
+ container.copyFeedbackTimer = setTimeout(function() {
21
+ container.classList.remove("is-copied");
22
+ feedback.textContent = "";
23
+ container.copyFeedbackTimer = null;
24
+ }, 1600);
25
+ }
26
+
27
+ function copyText(text) {
28
+ if (navigator.clipboard && navigator.clipboard.writeText) {
29
+ return navigator.clipboard.writeText(text);
30
+ }
31
+
32
+ return new Promise(function(resolve, reject) {
33
+ try {
34
+ var input = document.createElement("textarea");
35
+ input.value = text;
36
+ input.setAttribute("readonly", "");
37
+ input.style.position = "absolute";
38
+ input.style.left = "-9999px";
39
+ document.body.appendChild(input);
40
+ input.select();
41
+ document.execCommand("copy");
42
+ document.body.removeChild(input);
43
+ resolve();
44
+ } catch (error) {
45
+ reject(error);
46
+ }
47
+ });
48
+ }
49
+
50
+ function copyCell(trigger) {
51
+ var text = trigger.getAttribute("data-copy-text");
52
+ if (!text) return;
53
+
54
+ copyText(text).then(function() {
55
+ showCopiedFeedback(trigger);
56
+ dispatchCopyEvent("yummy-guide-administrate:copied", {
57
+ text: text,
58
+ trigger: trigger
59
+ });
60
+ }).catch(function(error) {
61
+ dispatchCopyEvent("yummy-guide-administrate:copy-error", {
62
+ text: text,
63
+ trigger: trigger,
64
+ error: error
65
+ });
66
+ });
67
+ }
68
+
69
+ document.addEventListener("click", function(event) {
70
+ var trigger = event.target.closest("[data-behavior='copy-cell']");
71
+ if (!trigger) return;
72
+
73
+ event.preventDefault();
74
+ event.stopPropagation();
75
+ if (typeof event.stopImmediatePropagation === "function") {
76
+ event.stopImmediatePropagation();
77
+ }
78
+
79
+ copyCell(trigger);
80
+ }, true);
81
+ })();
@@ -130,12 +130,31 @@
130
130
  }
131
131
 
132
132
  if (document.readyState === "loading") {
133
- document.addEventListener("DOMContentLoaded", initializeFromDocument, { once: true });
133
+ document.addEventListener("DOMContentLoaded", initializeFromDocument);
134
134
  } else {
135
135
  initializeFromDocument();
136
136
  }
137
137
 
138
138
  document.addEventListener("turbo:load", initializeFromDocument);
139
139
  window.addEventListener("resize", initializeFromDocument);
140
- })();
141
140
 
141
+ if (window.MutationObserver) {
142
+ var mutationObserver = new MutationObserver(function(mutations) {
143
+ mutations.forEach(function(mutation) {
144
+ mutation.addedNodes.forEach(function(node) {
145
+ if (node.nodeType === Node.ELEMENT_NODE) {
146
+ initializeStickyColumns(node);
147
+ }
148
+ });
149
+ });
150
+ });
151
+
152
+ mutationObserver.observe(document.documentElement, {
153
+ childList: true,
154
+ subtree: true
155
+ });
156
+ }
157
+
158
+ setTimeout(initializeFromDocument, 100);
159
+ setTimeout(initializeFromDocument, 300);
160
+ })();