zutils 3.0.7 → 4.0.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 +4 -4
- data/README.md +397 -20
- data/app/assets/stylesheets/zutils.scss +110 -2
- data/app/views/shared/_action_links.html.erb +43 -44
- data/app/views/shared/_btn_action_links.html.erb +130 -75
- data/app/views/shared/_card_list.html.erb +21 -54
- data/app/views/shared/_dropdown_menu.html.erb +95 -0
- data/app/views/shared/_empty_state.html.erb +15 -0
- data/app/views/shared/_empty_state_card.html.erb +29 -0
- data/app/views/shared/_fields.html.erb +48 -47
- data/app/views/shared/_file_chooser.html.erb +23 -0
- data/app/views/shared/_flash_only_toastr.html.erb +3 -0
- data/app/views/shared/_form.html.erb +28 -17
- data/app/views/shared/_index.html.erb +60 -68
- data/app/views/shared/_list.html.erb +164 -144
- data/app/views/shared/_modal.html.erb +26 -24
- data/app/views/shared/_pagination.html.erb +90 -0
- data/app/views/shared/_search_form.html.erb +84 -39
- data/app/views/shared/_show.html.erb +75 -182
- data/app/views/shared/_turbo_modal.html.erb +21 -0
- data/lib/zutils/engine.rb +3 -1
- data/lib/zutils/helpers.rb +173 -0
- data/lib/zutils/version.rb +1 -1
- data/lib/zutils.rb +2 -2
- metadata +27 -22
- data/.gitignore +0 -8
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -6
- data/Rakefile +0 -2
- data/app/assets/javascripts/zutils.js +0 -86
- data/zutils.gemspec +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f1ec2fd6ee82b24cc7e0d76d8e1936afac86acd47506d3403ac5ac3d3eb57e9
|
|
4
|
+
data.tar.gz: 26aab57eb436ee26c803c83668de402ae4917e533ddc59d0c36e61a4910abd45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9ea17a806dd17cef18536c8e7c1c24e0607049e31e4952d5da7ee3e30ee79886db58a08c0f62e184e80e94c8cfc8eedf138cb3bceef606fea7fc7d9a7a49cd9
|
|
7
|
+
data.tar.gz: bd712d0a0888f5a7d2169a9f4f3b2970dc821bada74884e46fe33aa737d141e049bf7c7aa82317495b4dc7e120c3a282a3b4f07bb6ef12c985f3742e8bf22493
|
data/README.md
CHANGED
|
@@ -1,43 +1,420 @@
|
|
|
1
1
|
# Zutils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Engine Rails com partials Bootstrap 5.3 e helpers reutilizáveis. Os controllers Stimulus
|
|
4
|
+
são distribuídos separadamente no pacote npm **`@zezim/zutils`**.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## O que a gem entrega
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
- **Views** (`app/views/shared/*`): partials prontos para CRUDs — listagem, formulário,
|
|
9
|
+
detalhes, pesquisa, paginação, ações, flash, modais, file chooser e estado vazio.
|
|
10
|
+
- **Helpers** (`lib/zutils/helpers.rb`): `bootstrap_flash`, `eval_with_rescue`,
|
|
11
|
+
`display`/`display_boolean`, `menu_activated?`, `section_header` e mais.
|
|
12
|
+
- **Assets** (`app/assets/stylesheets/zutils.scss`): integração do tom-select com o
|
|
13
|
+
visual do Bootstrap 5 e utilidades.
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
Os **controllers Stimulus** vivem no pacote **`@zezim/zutils`** — veja [zutils-js/README.md](zutils-js/README.md).
|
|
16
|
+
|
|
17
|
+
## Requisitos
|
|
18
|
+
|
|
19
|
+
| Dependência | Versão | Onde é usada |
|
|
20
|
+
| ------------------- | ------ | ------------------------------- |
|
|
21
|
+
| `rails` | >= 7.0 | engine |
|
|
22
|
+
| `bootstrap` (sass) | 5.3.x | partials |
|
|
23
|
+
| `stimulus-rails` | - | controllers JS |
|
|
24
|
+
| `simple_form` | - | `_form`, `_fields`, `_search_form` |
|
|
25
|
+
| `ransack` | - | `_list` (sort_link), `_search_form` |
|
|
26
|
+
| `cancancan` | - | `_btn_action_links`, `_action_links` (`can?`) |
|
|
27
|
+
| `kaminari` **ou** `pagy` | - | `_pagination`, `_index` |
|
|
28
|
+
| `active_storage` | - | `_list`, `_show` (anexos) |
|
|
29
|
+
| `tom-select` | - | `zutils.scss` |
|
|
30
|
+
|
|
31
|
+
### Dependências JS
|
|
32
|
+
|
|
33
|
+
Os controllers vêm do pacote `@zezim/zutils`, que declara as libs como `peerDependencies`
|
|
34
|
+
opcionais. Instale na app apenas as dos controllers que for usar:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// app/javascript/controllers/index.js — exemplos
|
|
38
|
+
import ToastrController from "@zezim/zutils/controllers/toastr"
|
|
39
|
+
import SweetAlertController from "@zezim/zutils/controllers/sweetalert"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### CSS das bibliotecas JS
|
|
43
|
+
|
|
44
|
+
Alguns controllers dependem de estilos que a **aplicação** deve importar no `application.scss`:
|
|
45
|
+
|
|
46
|
+
```scss
|
|
47
|
+
// app/assets/stylesheets/application.scss (com --load-path=node_modules)
|
|
48
|
+
@import 'bootstrap-fileinput/css/fileinput'; // fileinput
|
|
49
|
+
@import 'ckeditor5/dist/ckeditor5.css'; // ckeditor
|
|
50
|
+
@import 'tom-select/dist/css/tom-select.bootstrap5'; // tom-select, tom-select-remote
|
|
51
|
+
@import "toastr/build/toastr"; // toastr
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Sem esses imports, os controllers carregam (e o `data-controller` funciona) mas sem o
|
|
55
|
+
estilo visual (ex.: o CKEditor aparece como um `<textarea>` puro e o fileinput sem layout
|
|
56
|
+
de upload). Veja também [zutils-js/README.md](zutils-js/README.md).
|
|
57
|
+
|
|
58
|
+
## Instalação
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
# Gemfile
|
|
62
|
+
gem "zutils"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
bundle install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A engine é auto-carregada: views via `render "shared/..."` e helpers via `ActionView`.
|
|
70
|
+
|
|
71
|
+
### SASS
|
|
72
|
+
|
|
73
|
+
```scss
|
|
74
|
+
// app/assets/stylesheets/application.scss
|
|
75
|
+
@import "zutils";
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Uso — Views
|
|
79
|
+
|
|
80
|
+
### Listagem (`shared/index`, `shared/list`)
|
|
81
|
+
|
|
82
|
+
```erb
|
|
83
|
+
<%= render "shared/index",
|
|
84
|
+
list: @users,
|
|
85
|
+
pagy: @pagy,
|
|
86
|
+
sort_all: true,
|
|
87
|
+
show_new: true,
|
|
88
|
+
excluded_columns: %w[password_digest]
|
|
89
|
+
%>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Parâmetro | Padrão | Descrição |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `list` | - | collection paginada |
|
|
95
|
+
| `pagy` | `@pagy` | objeto Pagy (ou Kaminari) |
|
|
96
|
+
| `show_new` | `!hide.include?("header_new")` | botão "Cadastrar" |
|
|
97
|
+
| `hide` | `%w[new back delete_right]` | ações ocultas (`header`, `new`, `back`, `delete`) |
|
|
98
|
+
| `card_color` | `"primary"` | cor do card / botões |
|
|
99
|
+
| `excluded_columns` / `additional_columns` / `show_columns` | `[]` | colunas |
|
|
100
|
+
| `sort_all` / `sort_fields` | `false` / `[]` | ordenação |
|
|
101
|
+
| `controller` | `"bootstrap-table"` | data-controller do card |
|
|
102
|
+
| `table_class` | `"table table-striped align-middle bootstrap-table"` | classes da tabela |
|
|
103
|
+
| `hide_actions` | `false` | oculta coluna de ações |
|
|
104
|
+
| `additional_actions` | `[]` | ações extras |
|
|
105
|
+
| `size` | `"sm"` | tamanho dos botões |
|
|
106
|
+
|
|
107
|
+
Colunas como hashes:
|
|
108
|
+
|
|
109
|
+
```erb
|
|
110
|
+
show_columns: [
|
|
111
|
+
{ attribute: "id", align: "right" },
|
|
112
|
+
"name",
|
|
113
|
+
{ attribute: "created_at", format: :long }
|
|
114
|
+
]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Convenções: `*_id` mostra o nome do registro associado, `*_cents` formata moeda,
|
|
118
|
+
atributos `attached?` (ActiveStorage) viram botão de download, `*_currency` é ignorada.
|
|
119
|
+
|
|
120
|
+
```erb
|
|
121
|
+
<%# somente a tabela %>
|
|
122
|
+
<%= render "shared/list", list: @users %>
|
|
123
|
+
|
|
124
|
+
<%# alias legado que delega a shared/index %>
|
|
125
|
+
<%= render "shared/card_list", list: @users %>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Formulário (`shared/form`, `shared/fields`)
|
|
129
|
+
|
|
130
|
+
```erb
|
|
131
|
+
<%= render "shared/form", object: @user %>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
| Parâmetro | Padrão | Descrição |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `object` | - | objeto do formulário |
|
|
137
|
+
| `excluded_columns` / `additional_columns` / `restricted_columns` | `[]` / `{}` | colunas |
|
|
138
|
+
| `nested_resource` / `parent_resource` | `false` | recurso aninhado |
|
|
139
|
+
| `back_link` | `false` | cancelar via `history.back()` |
|
|
140
|
+
| `card_color` | `"primary"` | cor do card e botão salvar |
|
|
141
|
+
|
|
142
|
+
Os campos usam `form_fields` na model:
|
|
10
143
|
|
|
11
144
|
```ruby
|
|
12
|
-
|
|
145
|
+
# app/models/user.rb
|
|
146
|
+
def self.form_fields
|
|
147
|
+
{ name: { size: 6 }, email: { size: 6 } }
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Detalhes (`shared/show`)
|
|
152
|
+
|
|
153
|
+
```erb
|
|
154
|
+
<%= render "shared/show", object: @user %>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| Parâmetro | Padrão | Descrição |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `object` | - | registro |
|
|
160
|
+
| `color` | `"primary"` | cor do topo |
|
|
161
|
+
| `hide` | `%w[new show]` | ações ocultas (`top_bar`, `bottom_bar`) |
|
|
162
|
+
| `buttons_size` | `"md"` | tamanho dos botões |
|
|
163
|
+
| `edit_color` / `delete_color` | `"btn-warning"` / `"btn-outline-danger"` | cores |
|
|
164
|
+
| `excluded_columns` / `additional_columns` / `show_columns` | - | colunas |
|
|
165
|
+
| `nested_resource` / `relationships` | `false` / `{}` | recursos aninhados |
|
|
166
|
+
|
|
167
|
+
Se existir `app/views/users/_user.html.erb`, ele é renderizado no lugar da tabela.
|
|
168
|
+
|
|
169
|
+
### Pesquisa (`shared/search_form`)
|
|
170
|
+
|
|
171
|
+
```erb
|
|
172
|
+
<%= render "shared/search_form",
|
|
173
|
+
url: users_path,
|
|
174
|
+
search_fields: [
|
|
175
|
+
{ name: :name_cont, attrs: { placeholder: "Nome" } },
|
|
176
|
+
{ name: :email_cont, attrs: { placeholder: "E-mail" } }
|
|
177
|
+
]
|
|
178
|
+
%>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Parâmetro | Padrão | Descrição |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `search_object` | `@q` | objeto ransack |
|
|
184
|
+
| `url` | - | action do form |
|
|
185
|
+
| `opened` | `false` | filtros abertos |
|
|
186
|
+
| `search_fields` | `[]` | campos avançados |
|
|
187
|
+
| `main_field` | `{ name: :id_eq }` | campo principal |
|
|
188
|
+
| `card_color` | `"primary"` | cor do botão "Aplicar" |
|
|
189
|
+
|
|
190
|
+
Requer `data-controller="filtering"`.
|
|
191
|
+
|
|
192
|
+
### Paginação (`shared/pagination`)
|
|
193
|
+
|
|
194
|
+
```erb
|
|
195
|
+
<%= render "shared/pagination", list: @users, pagy: @pagy %>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Preserva query params (exceto page/items), suporta Kaminari ou Pagy.
|
|
199
|
+
|
|
200
|
+
### Botões de ação (`shared/btn_action_links`, `shared/action_links`)
|
|
201
|
+
|
|
202
|
+
```erb
|
|
203
|
+
<%= render "shared/btn_action_links", object: @user, labels: true, hide: %w[delete] %>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
| Parâmetro | Padrão | Descrição |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `object` | - | registro (aceita array p/ rotas aninhadas) |
|
|
209
|
+
| `labels` | `false` | mostra texto |
|
|
210
|
+
| `hide` | `[]` | ações a ocultar |
|
|
211
|
+
| `size` | `"sm"` | tamanho |
|
|
212
|
+
| `extra_actions` | `{}` | ações customizáveis |
|
|
213
|
+
| `back_color` / `show_color` / `new_color` / `edit_color` / `delete_color` | ver _btn | classes |
|
|
214
|
+
|
|
215
|
+
```erb
|
|
216
|
+
extra_actions: {
|
|
217
|
+
publish: {
|
|
218
|
+
icon: "fa-check", label: "Publicar", class: "btn-success",
|
|
219
|
+
side: :right, path: -> { publish_user_path(object) }, method: :post
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Dropdown (`data-controller="dropdown"` — requer `tippy.js`):
|
|
225
|
+
|
|
226
|
+
```erb
|
|
227
|
+
<%= render "shared/dropdown_menu", object: @user, theme: "light" do %>
|
|
228
|
+
<li><%= link_to "Custom", custom_path(object), class: "dropdown-item" %></li>
|
|
229
|
+
<% end %>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Flash (`shared/flash`, `shared/flash_only_toastr`)
|
|
233
|
+
|
|
234
|
+
```erb
|
|
235
|
+
<%# no layout — toastr (padrão) %>
|
|
236
|
+
<%= render "shared/flash_only_toastr" %>
|
|
237
|
+
|
|
238
|
+
<%# Alertas Bootstrap + toasts %>
|
|
239
|
+
<%= render "shared/flash" %>
|
|
240
|
+
|
|
241
|
+
<%# com SweetAlert2 %>
|
|
242
|
+
<%= render "shared/flash_only_toastr", notification: :sweetalert %>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
O helper `bootstrap_flash` gera os alertas com `data-kind`/`data-text` consumidos
|
|
246
|
+
pelos controllers `toastr` ou `sweetalert`. Em Turbo:
|
|
247
|
+
|
|
248
|
+
```erb
|
|
249
|
+
<%= turbo_stream.prepend "flash", partial: "shared/flash_only_toastr", locals: { notification: :toastr } %>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Modais (`shared/modal`, `shared/turbo_modal`)
|
|
253
|
+
|
|
254
|
+
```erb
|
|
255
|
+
<%= render "shared/modal", modal_id: "meu-modal", modal_title: "Título", modal_size: "lg" do %>
|
|
256
|
+
<p>Conteúdo</p>
|
|
257
|
+
<% end %>
|
|
258
|
+
|
|
259
|
+
<%= render "shared/turbo_modal", title: "Editar" do %>
|
|
260
|
+
<%= render "form" %>
|
|
261
|
+
<% end %>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### File chooser (`shared/file_chooser`)
|
|
265
|
+
|
|
266
|
+
```erb
|
|
267
|
+
<%= render "shared/file_chooser", f: f, attribute: :avatar, label: "Foto", kind: "imagefileinput" %>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
| Parâmetro | Padrão | Descrição |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `f` | - | form builder (simple_form) |
|
|
273
|
+
| `attribute` | - | atributo ActiveStorage |
|
|
274
|
+
| `label` | `""` | label do campo |
|
|
275
|
+
| `required` | `false` | campo obrigatório |
|
|
276
|
+
| `kind` | `"pdffileinput"` | classe p/ fileinput: `imagefileinput`, `anyfileinput`, `pdffileinput`, `xlsfileinput` |
|
|
277
|
+
| `wrapper_html` | `{ class: "" }` | classes do wrapper |
|
|
278
|
+
|
|
279
|
+
Requer `data-controller="fileinput"` (controller `fileinput` do `@zezim/zutils`).
|
|
280
|
+
|
|
281
|
+
### Estado vazio (`shared/empty_state`, `shared/empty_state_card`)
|
|
282
|
+
|
|
283
|
+
```erb
|
|
284
|
+
<%# versão simples %>
|
|
285
|
+
<%= render "shared/empty_state", icon: "far fa-circle-xmark", message: "Nenhum registro" %>
|
|
286
|
+
|
|
287
|
+
<%# versão card com CTA %>
|
|
288
|
+
<%= render "shared/empty_state_card",
|
|
289
|
+
title: "Nada aqui!",
|
|
290
|
+
message: "Clique para criar.",
|
|
291
|
+
icon: "fa fa-inbox",
|
|
292
|
+
btn_title: "Criar",
|
|
293
|
+
btn_url: new_user_path,
|
|
294
|
+
btn_icon: "fa-plus",
|
|
295
|
+
btn_color: "btn-primary"
|
|
296
|
+
%>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| Parâmetro | Padrão | Descrição |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `title` | `""` | título |
|
|
302
|
+
| `message` | `""` | mensagem descritiva |
|
|
303
|
+
| `icon` | `"fa fa-inbox"` | ícone |
|
|
304
|
+
| `btn_title` | `"Criar"` | texto do botão |
|
|
305
|
+
| `btn_url` | `""` | link do botão (se vazio, não renderiza) |
|
|
306
|
+
| `btn_icon` | `"fa-plus"` | ícone do botão |
|
|
307
|
+
| `btn_color` | `"btn-primary"` | classe do botão |
|
|
308
|
+
| `icon_style` | `""` | classe extra no ícone |
|
|
309
|
+
| `border_color` | `""` | classe na borda |
|
|
310
|
+
|
|
311
|
+
## Uso — Helpers
|
|
312
|
+
|
|
313
|
+
| Helper | Descrição |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `bootstrap_flash` | gera alertas Bootstrap 5 com `data-kind`/`data-text` (consumido por toastr ou sweetalert). Aceita opções: `success_title`, `error_title`, `alert_title`, `info_title`, `*_icon`. |
|
|
316
|
+
| `eval_with_rescue(code)` | avalia código Ruby, retorna `"error"` em exceção |
|
|
317
|
+
| `render_turbo_stream_flash_messages(local:)` / `render_turbo_stream_flash_messages_only_toastr` | prepend turbo_stream com flash |
|
|
318
|
+
| `nested_dom_id(*args)` | gera dom_id para recursos aninhados |
|
|
319
|
+
| `menu_activated?(menu_item)` | avalia `active_test` de item de menu + children |
|
|
320
|
+
| `display(object, field, options)` | bloco "ícone + título + valor" |
|
|
321
|
+
| `display_boolean(object, field, options)` | badge Bootstrap 5 (`bg-success`/`bg-danger`) |
|
|
322
|
+
| `display_boolean_icon(object, field, options)` | badge com ícone + tooltip |
|
|
323
|
+
| `interactive_send(obj, field)` | getter por dot-path (ex: `"address.city.name"`), com l10n de datas |
|
|
324
|
+
| `section_header(title, icon:, color:, border:)` | subtítulo com ícone e cor |
|
|
325
|
+
| `smart_date_tag(date, icon:, label:, text_class:)` | tag de data com ano opcional |
|
|
326
|
+
| `format_date(value)` | converte para `DD/MM/YYYY` |
|
|
327
|
+
|
|
328
|
+
```erb
|
|
329
|
+
<%# display %>
|
|
330
|
+
<%= display @user, :name %>
|
|
331
|
+
<%= display @user, :active, value: display_boolean(@user, :active) %>
|
|
332
|
+
<%= section_header "Endereço", icon: "fa-map-marker", color: "primary" %>
|
|
13
333
|
```
|
|
14
334
|
|
|
15
|
-
|
|
335
|
+
## Uso — Controllers Stimulus
|
|
336
|
+
|
|
337
|
+
Instale o pacote `@zezim/zutils` e registre seletivamente no Stimulus:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
// app/javascript/controllers/index.js
|
|
341
|
+
import { application } from "./application"
|
|
342
|
+
|
|
343
|
+
import FilteringController from "@zezim/zutils/controllers/filtering"
|
|
344
|
+
application.register("filtering", FilteringController)
|
|
16
345
|
|
|
17
|
-
|
|
346
|
+
import ToastrController from "@zezim/zutils/controllers/toastr"
|
|
347
|
+
application.register("toastr", ToastrController)
|
|
18
348
|
|
|
19
|
-
|
|
349
|
+
import SweetAlertController from "@zezim/zutils/controllers/sweetalert"
|
|
350
|
+
application.register("sweetalert", SweetAlertController)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Lista completa dos 17 controllers disponíveis (todos documentados em
|
|
354
|
+
[zutils-js/README.md](zutils-js/README.md)):
|
|
355
|
+
|
|
356
|
+
| Subpath (`@zezim/zutils/controllers/...`) | `data-controller` | Requer lib |
|
|
357
|
+
|---|---|---|
|
|
358
|
+
| `filtering` | `filtering` | `bootstrap` |
|
|
359
|
+
| `toastr` | `toastr` | `toastr` |
|
|
360
|
+
| `sweetalert` | `sweetalert` | `sweetalert2`, `@hotwired/turbo-rails` |
|
|
361
|
+
| `bootstrap-table` | `bootstrap-table` | `bootstrap-table`, `jquery` |
|
|
362
|
+
| `dropdown` | `dropdown` | `tippy.js` |
|
|
363
|
+
| `tooltip` | `tooltip` | `bootstrap` |
|
|
364
|
+
| `tooltips` | `tooltips` | `bootstrap` |
|
|
365
|
+
| `tom-select` | `tom-select` | `tom-select` |
|
|
366
|
+
| `tom-select-remote` | `tom-select-remote` | `tom-select` |
|
|
367
|
+
| `fileinput` | `fileinput` | `bootstrap-fileinput`, `jquery` |
|
|
368
|
+
| `mask` | `mask` | `inputmask` |
|
|
369
|
+
| `modal` | `modal` | `bootstrap` |
|
|
370
|
+
| `ckeditor` | `ckeditor` | `ckeditor5` |
|
|
371
|
+
| `adminlte` | `adminlte` | `admin-lte` (JS global) |
|
|
372
|
+
| `confirm` | `confirm` | `sweetalert2` |
|
|
373
|
+
| `auto-submit` | `auto-submit` | `@rails/request.js` |
|
|
374
|
+
| `dark-mode` | `dark-mode` | — |
|
|
20
375
|
|
|
21
|
-
|
|
376
|
+
### `filtering`
|
|
22
377
|
|
|
23
|
-
|
|
378
|
+
```erb
|
|
379
|
+
<div data-controller="filtering">
|
|
380
|
+
<div data-filtering-target="body" class="collapse">
|
|
381
|
+
<!-- filtros detalhados -->
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
```
|
|
24
385
|
|
|
25
|
-
|
|
386
|
+
- alvo `body`: painel collapse
|
|
387
|
+
- alvo `icon`: `<i>` que alterna `fa-sliders-h` ↔ `fa-angle-down`
|
|
388
|
+
- alvo `filters`: badges de filtros ativos
|
|
389
|
+
- botão: `data-action="click->filtering#toggle"`
|
|
26
390
|
|
|
27
|
-
|
|
391
|
+
### `bootstrap-table`
|
|
28
392
|
|
|
29
|
-
|
|
393
|
+
Inicializa qualquer tabela `.bootstrap-table-mobile` dentro do elemento.
|
|
30
394
|
|
|
31
|
-
|
|
395
|
+
### `dropdown`
|
|
32
396
|
|
|
33
|
-
|
|
397
|
+
```erb
|
|
398
|
+
<div data-controller="dropdown" data-dropdown-theme-value="light">
|
|
399
|
+
<button>⋮</button>
|
|
400
|
+
<template data-dropdown-target="template">
|
|
401
|
+
<ul class="dropdown-menu show"><!-- itens --></ul>
|
|
402
|
+
</template>
|
|
403
|
+
</div>
|
|
404
|
+
```
|
|
34
405
|
|
|
35
|
-
|
|
406
|
+
## Desenvolvimento
|
|
36
407
|
|
|
37
|
-
|
|
408
|
+
```sh
|
|
409
|
+
bin/setup # bundle install
|
|
410
|
+
bin/console # IRB com a gem
|
|
411
|
+
bundle exec rake build # .gem em pkg/
|
|
412
|
+
bundle exec rake install # instala localmente
|
|
413
|
+
```
|
|
38
414
|
|
|
39
|
-
|
|
415
|
+
Publicar a gem: atualize `lib/zutils/version.rb` e `rake release`.
|
|
416
|
+
Publicar o JS: veja [zutils-js/README.md](zutils-js/README.md).
|
|
40
417
|
|
|
41
|
-
##
|
|
418
|
+
## Licença
|
|
42
419
|
|
|
43
|
-
|
|
420
|
+
MIT — veja `LICENSE.txt`.
|
|
@@ -1,2 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Utilitários TomSelect (a importação do CSS é responsabilidade da aplicação)
|
|
2
|
+
.ts-wrapper,
|
|
3
|
+
.tom-select {
|
|
4
|
+
.form-select,
|
|
5
|
+
.form-control {
|
|
6
|
+
width: 100%;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Tabela bootstrap-table
|
|
11
|
+
.bootstrap-table .page-list,
|
|
12
|
+
.bootstrap-table .fixed-table-toolbar,
|
|
13
|
+
.bootstrap-table .sticky-header {
|
|
14
|
+
background: transparent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.bootstrap-table .fixed-table-toolbar .columns label {
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.bootstrap-table .table-responsive.bootstrap-table-mobile {
|
|
22
|
+
border: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Utilidades
|
|
26
|
+
.cursor-pointer,
|
|
27
|
+
.cp-pointer {
|
|
28
|
+
cursor: pointer !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.list-empty-state {
|
|
32
|
+
background: var(--bs-tertiary-bg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.border-dashed {
|
|
36
|
+
border-style: dashed !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Ransack: setas de ordenação nas cabeçalhos das tabelas (shared/list, shared/index)
|
|
40
|
+
.bootstrap-table .table thead th .asc,
|
|
41
|
+
.bootstrap-table .table thead th .desc {
|
|
42
|
+
background-image: unset !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Filtros ativos (shared/search_form)
|
|
46
|
+
.active-filters {
|
|
47
|
+
span {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
margin-right: 0.5em;
|
|
50
|
+
|
|
51
|
+
&:hover {
|
|
52
|
+
opacity: 0.8;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
i {
|
|
56
|
+
margin: 0px 2px 0.2px 2px;
|
|
57
|
+
font-size: 100%;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media (max-width: 768px) {
|
|
64
|
+
.active-filters span {
|
|
65
|
+
white-space: normal;
|
|
66
|
+
margin-top: 0.2em;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Dropdown via tippy (shared/dropdown_menu)
|
|
71
|
+
.tippy-box[data-theme~='dark'] {
|
|
72
|
+
background-color: var(--bs-body-bg);
|
|
73
|
+
color: var(--bs-body-color);
|
|
74
|
+
border-radius: 0.375rem;
|
|
75
|
+
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, .15);
|
|
76
|
+
padding: 0.25rem 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.tippy-box[data-theme~='dark'] .dropdown-item {
|
|
80
|
+
padding: 0.25rem 0.5rem;
|
|
81
|
+
font-size: 0.875rem;
|
|
82
|
+
color: var(--bs-body-color);
|
|
83
|
+
|
|
84
|
+
&:hover {
|
|
85
|
+
background-color: var(--bs-secondary-bg);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.tippy-content .dropdown-menu {
|
|
90
|
+
display: block;
|
|
91
|
+
position: static;
|
|
92
|
+
float: none;
|
|
93
|
+
border: none;
|
|
94
|
+
background-color: transparent;
|
|
95
|
+
margin: 0;
|
|
96
|
+
padding: 4px;
|
|
97
|
+
min-width: 140px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.tippy-content .dropdown-item {
|
|
101
|
+
padding: 6px 12px !important;
|
|
102
|
+
|
|
103
|
+
&:hover {
|
|
104
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.tippy-arrow::before {
|
|
109
|
+
color: var(--bs-body-bg) !important;
|
|
110
|
+
}
|
|
@@ -1,56 +1,55 @@
|
|
|
1
|
-
<% additional_actions ||= {} %>
|
|
2
|
-
|
|
3
1
|
<% hide_read_action ||= false %>
|
|
4
2
|
<% hide_edit_action ||= false %>
|
|
5
3
|
<% hide_destroy_action ||= false %>
|
|
4
|
+
<% vertical ||= false %>
|
|
5
|
+
<% size ||= "sm" %>
|
|
6
|
+
<% hide ||= [] %>
|
|
7
|
+
<% additional_actions ||= {} %>
|
|
6
8
|
|
|
7
|
-
<div class="btn-group">
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<ul class="dropdown-menu" role="menu">
|
|
14
|
-
<% if !hide_read_action && can?(:read, (object.class == Array ? object.last.class : object.class)) %>
|
|
15
|
-
<li>
|
|
16
|
-
<%= link_to object, class: 'dropdown-item', data: { toggle: 'tooltip', placement: 'top', title: 'Visualizar' } do %>
|
|
17
|
-
<i class="fa fa-eye"></i> Visualizar
|
|
18
|
-
<% end %>
|
|
9
|
+
<div class="btn-group<%= '-vertical' if vertical %> gap-1" data-turbo="true">
|
|
10
|
+
<% unless hide.include?("show") || hide_read_action %>
|
|
11
|
+
<% if can?(:read, (object.class == Array ? object.last : object)) %>
|
|
12
|
+
<%= link_to object, class: "btn btn-#{size} btn-outline-secondary", data: { "bs-toggle": "tooltip", "bs-placement": "top", "bs-title": "Visualizar" } do %>
|
|
13
|
+
<i class="fa fa-eye"></i>
|
|
14
|
+
<% end %>
|
|
19
15
|
<% end %>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<% else %>
|
|
28
|
-
<%= link_to eval_with_rescue("edit_#{[object].flatten.map{|x| x.class.to_s.underscore}.join("_")}_path(#{object.id})"), class: 'dropdown-item', data: { toggle: 'tooltip', placement: 'top', title: 'Editar' } do %>
|
|
29
|
-
<i class="fa fa-pencil fa-edit"></i> Editar
|
|
30
|
-
<% end %>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<% unless hide.include?("edit") || hide_edit_action %>
|
|
19
|
+
<% if can?(:edit, (object.class == Array ? object.last : object)) %>
|
|
20
|
+
<% if object.class == Array %>
|
|
21
|
+
<%= link_to eval_with_rescue("edit_#{[object].flatten.map{|x| x.class.to_s.underscore}.join("_")}_path(#{object[0].id}, #{object[1].id})"), class: "btn btn-#{size} btn-outline-warning", data: { "bs-toggle": "tooltip", "bs-placement": "top", "bs-title": "Editar" } do %>
|
|
22
|
+
<i class="fa fa-pencil fa-edit"></i>
|
|
31
23
|
<% end %>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<li>
|
|
36
|
-
<%= link_to object, method: :delete, class: 'dropdown-item', data: { confirm: 'Tem certeza?', toggle: 'tooltip', placement: 'top', title: 'Apagar' } do %>
|
|
37
|
-
<i class="fa fa-trash"></i> Apagar
|
|
24
|
+
<% else %>
|
|
25
|
+
<%= link_to eval_with_rescue("edit_#{[object].flatten.map{|x| x.class.to_s.underscore}.join("_")}_path(#{object.id})"), class: "btn btn-#{size} btn-outline-warning", data: { "bs-toggle": "tooltip", "bs-placement": "top", "bs-title": "Editar" } do %>
|
|
26
|
+
<i class="fa fa-pencil fa-edit"></i>
|
|
38
27
|
<% end %>
|
|
39
|
-
|
|
28
|
+
<% end %>
|
|
40
29
|
<% end %>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
<% unless hide.include?("delete") || hide_destroy_action %>
|
|
33
|
+
<% if can?(:destroy, (object.class == Array ? object.last : object)) %>
|
|
34
|
+
<%= button_to object, method: :delete, class: "btn btn-#{size} btn-outline-danger",
|
|
35
|
+
form: { data: { turbo_confirm: 'Tem certeza?' }},
|
|
36
|
+
data: { "bs-toggle": "tooltip", "bs-placement": "top", "bs-title": "Apagar" } do %>
|
|
37
|
+
<i class="fa fa-trash"></i>
|
|
50
38
|
<% end %>
|
|
51
39
|
<% end %>
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<% additional_actions.each do |key, value| %>
|
|
43
|
+
<% if can? key, (object.class == Array ? object.last : object) %>
|
|
44
|
+
<%= link_to eval_with_rescue("#{value[:link]}(#{object.id})"),
|
|
45
|
+
value[:options],
|
|
46
|
+
class: "btn btn-#{size} #{value[:class] || 'btn-outline-primary'}" do %>
|
|
47
|
+
<i class="fa fa-<%= value[:icon] %>"></i>
|
|
48
|
+
<% end %>
|
|
54
49
|
<% end %>
|
|
55
|
-
|
|
50
|
+
<% end %>
|
|
51
|
+
|
|
52
|
+
<% if block_given? %>
|
|
53
|
+
<%= yield %>
|
|
54
|
+
<% end %>
|
|
56
55
|
</div>
|