testimonials 0.8.0 → 0.8.2
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/CHANGELOG.md +21 -0
- data/app/views/layouts/testimonials/application.html.erb +14 -12
- data/lib/generators/testimonials/install/templates/create_testimonials_tables.rb.tt +0 -1
- data/lib/generators/testimonials/tenant/templates/add_tenant_to_testimonials.rb.tt +0 -1
- data/lib/testimonials/dashboard.css +122 -160
- data/lib/testimonials/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6e6a966ec88019419566c0524b32c1e2cfbc01126ad9d7ee807063343fbadec
|
|
4
|
+
data.tar.gz: 1a3982c027490e0e31f342058e390c4aec208f9a90b4242998629aa6718a3504
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c92786e0beccb39fe665405ce100d997e0db155a7bc2c56e881747f417f705414b57cfac650afa37d4473beec64a2d61c916a49e3ad489f92aaed889bd5e267
|
|
7
|
+
data.tar.gz: 2e3ee818dcbb6c619e3efdc1740ddec85421c0194b0778d72767d7935f0547a88fdf19a247bc8479a06d9fcd7e74199448b350b5368d41bcc9f3ec250b48742a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
- Dropped the redundant `(tenant, status)` index from the install and tenant
|
|
6
|
+
migration templates. A B-tree serves any leftmost prefix, so
|
|
7
|
+
`(tenant, status, consent_given)` already covers it, and every scope on the
|
|
8
|
+
model starts with `for_tenant` — carrying both only cost write time and disk.
|
|
9
|
+
Flagged by `active_record_doctor` in a host app.
|
|
10
|
+
- Existing installs keep the index until they drop it themselves; nothing
|
|
11
|
+
breaks either way. To reclaim it:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class RemoveRedundantTestimonialsIndex < ActiveRecord::Migration[8.0]
|
|
15
|
+
def change
|
|
16
|
+
remove_index :testimonials_testimonials, %i[tenant status]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- A generator test now fails the build if any index is a leftmost prefix of
|
|
22
|
+
another on the same table.
|
|
23
|
+
|
|
3
24
|
## 0.8.0
|
|
4
25
|
|
|
5
26
|
- **`config.admin_layout` now works on its own.** The dashboard's stylesheet and
|
|
@@ -9,19 +9,21 @@
|
|
|
9
9
|
(testimonials/shared/_dashboard), not here, so they survive a host
|
|
10
10
|
replacing this layout via config.admin_layout. %>
|
|
11
11
|
</head>
|
|
12
|
-
<body class="<%= content_for?(:body_class) ? yield(:body_class) : '' %>">
|
|
13
|
-
<div class="tml-
|
|
14
|
-
<div class="
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
<body class="tml-page <%= content_for?(:body_class) ? yield(:body_class) : '' %>">
|
|
13
|
+
<div class="tml-dashboard">
|
|
14
|
+
<div class="container">
|
|
15
|
+
<div class="nav">
|
|
16
|
+
<%= link_to t('testimonials.dashboard.title', default: 'Testimonials'), root_path,
|
|
17
|
+
class: ('active' unless controller_name == 'nps_responses') %>
|
|
18
|
+
<% if Testimonials.config.nps %>
|
|
19
|
+
<%= link_to t('testimonials.dashboard.nps', default: 'NPS'), nps_responses_path,
|
|
20
|
+
class: ('active' if controller_name == 'nps_responses') %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<%# The shareable links live on the index pages themselves, next to the
|
|
23
|
+
heading — a host admin_layout replaces this nav, not those. %>
|
|
24
|
+
</div>
|
|
25
|
+
<%= yield %>
|
|
23
26
|
</div>
|
|
24
|
-
<%= yield %>
|
|
25
27
|
</div>
|
|
26
28
|
</body>
|
|
27
29
|
</html>
|
|
@@ -24,7 +24,6 @@ class CreateTestimonialsTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
24
24
|
t.timestamps
|
|
25
25
|
end
|
|
26
26
|
add_index :testimonials_testimonials, :status
|
|
27
|
-
add_index :testimonials_testimonials, %i[tenant status]
|
|
28
27
|
add_index :testimonials_testimonials, %i[tenant status consent_given]
|
|
29
28
|
<% unless options[:skip_nps] -%>
|
|
30
29
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
class AddTenantToTestimonials < ActiveRecord::Migration<%= migration_version %>
|
|
4
4
|
def change
|
|
5
5
|
add_column :testimonials_testimonials, :tenant, :string
|
|
6
|
-
add_index :testimonials_testimonials, %i[tenant status]
|
|
7
6
|
add_index :testimonials_testimonials, %i[tenant status consent_given]
|
|
8
7
|
|
|
9
8
|
# The optional tables: an install run with --skip-nps or
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/* Dashboard styles.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
3
|
+
* Three layers, and the split is what lets a host replace the layout:
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* :root custom properties, all `--tml-` prefixed so they can
|
|
6
|
+
* neither overwrite a host's nor be overwritten by them.
|
|
7
|
+
* .tml-page the page frame (was bare html/body). Only the gem's own
|
|
8
|
+
* layout puts this class on <body>, so inside a host admin
|
|
9
|
+
* these rules simply do not apply.
|
|
10
|
+
* .tml-dashboard everything else, nested. Names like .container, .card
|
|
11
|
+
* and .tabs are confined to the dashboard's own markup, so
|
|
12
|
+
* loading this file inside a host cannot restyle its chrome.
|
|
12
13
|
*
|
|
13
|
-
* Nothing here styles a bare `body`, `a`, `table` or
|
|
14
|
-
* custom property is `tml-`-prefixed or nested, so loading this file inside a
|
|
15
|
-
* host admin cannot restyle the host's sidebar, topbar or anything else.
|
|
14
|
+
* Nothing here styles a bare `body`, `a`, `table` or `*` at the top level.
|
|
16
15
|
*/
|
|
17
16
|
|
|
17
|
+
|
|
18
18
|
:root {
|
|
19
19
|
color-scheme: light dark;
|
|
20
20
|
--tml-bg: #f6f7f9; --tml-surface: #fff; --tml-text: #1c2024; --tml-muted: #6b7280;
|
|
@@ -29,89 +29,115 @@
|
|
|
29
29
|
:root {
|
|
30
30
|
--tml-bg: #111418; --tml-surface: #1a1f26; --tml-text: #e6e8ea; --tml-muted: #9aa2ab;
|
|
31
31
|
--tml-border: #2a313a; --tml-accent: #3b82f6;
|
|
32
|
-
}
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
/* ── 1. Page shell (gem layout only) ─────────────────────────────────────── */
|
|
36
|
-
|
|
37
|
-
.tm-index, .tm-nps-index, .tm-show {
|
|
38
|
-
margin: 0; background: var(--tml-bg); color: var(--tml-text);
|
|
39
|
-
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
.tml-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.tml-
|
|
34
|
+
@media (max-width: 760px) {
|
|
35
|
+
.tml-page { font-size: 14px; }
|
|
36
|
+
.tm-index, .tm-nps-index, .tm-show { overflow-x: hidden; }
|
|
37
|
+
.tm-index, .tm-nps-index { overflow-y: auto; overflow-x: hidden; }
|
|
38
|
+
.tm-index, .tm-index .container,
|
|
39
|
+
.tm-nps-index, .tm-nps-index .container { min-height: 100vh; height: auto; }
|
|
40
|
+
.tm-index .container, .tm-nps-index .container { padding-bottom: 10px; }
|
|
41
|
+
.tml-dashboard {
|
|
42
|
+
& .container { padding: 14px 10px 24px; }
|
|
43
|
+
& .dashboard-detail,
|
|
44
|
+
& .testimonial-panel,
|
|
45
|
+
& .nps-panel,
|
|
46
|
+
& .detail-scroll,
|
|
47
|
+
& .card,
|
|
48
|
+
& dl,
|
|
49
|
+
& dd,
|
|
50
|
+
& .best-line-form,
|
|
51
|
+
& .best-line-form textarea { min-width: 0; max-width: 100%; }
|
|
52
|
+
& .testimonial-panel .card,
|
|
53
|
+
& .nps-panel .card { width: calc(100vw - 20px); }
|
|
54
|
+
& .best-line-form textarea { width: 100%; }
|
|
55
|
+
& .message,
|
|
56
|
+
& .best-line-hint,
|
|
57
|
+
& dd,
|
|
58
|
+
& .muted { overflow-wrap: anywhere; }
|
|
59
|
+
& .message,
|
|
60
|
+
& .best-line-hint,
|
|
61
|
+
& dd,
|
|
62
|
+
& .muted { max-width: calc(100vw - 54px); }
|
|
63
|
+
& .dashboard-shell { display: block; min-height: calc(100vh - 76px); }
|
|
64
|
+
& .dashboard-sidebar,
|
|
65
|
+
& .dashboard-detail { min-height: calc(100vh - 76px); }
|
|
66
|
+
& .dashboard-shell.has-selected .dashboard-sidebar { display: none; }
|
|
67
|
+
& .dashboard-shell:not(.has-selected) .dashboard-detail { display: none; }
|
|
68
|
+
& .record-list { overflow: visible; }
|
|
69
|
+
& .record-row { padding: 12px; }
|
|
70
|
+
& .record-meta { display: none; }
|
|
71
|
+
& .dashboard-detail .testimonial-panel,
|
|
72
|
+
& .dashboard-detail .nps-panel { min-height: calc(100vh - 64px); }
|
|
73
|
+
& .mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
74
|
+
& .panel-head h1 { font-size: 18px; }
|
|
75
|
+
& dl { grid-template-columns: 1fr; gap: 3px; }
|
|
76
|
+
& .stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
77
|
+
& .stat { min-width: 0; padding: 10px 12px; }
|
|
78
|
+
& .stat b { font-size: 22px; }
|
|
79
|
+
& .stat-nps { grid-column: 1 / -1; }
|
|
80
|
+
& .nps-bands { grid-template-columns: max-content 1fr; }
|
|
81
|
+
& .nps-bands b { grid-column: 2; }
|
|
82
|
+
& .nps-bands li > .muted { grid-column: 1 / -1; }
|
|
83
|
+
& .share-link { flex: 1 0 100%; margin-left: 0; text-align: center; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
49
86
|
|
|
50
|
-
/*
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
.tm-index, .tm-index .
|
|
54
|
-
.tm-nps-index, .tm-nps-index .
|
|
87
|
+
/* Page frame — the gem's own layout only. */
|
|
88
|
+
.tml-page { margin: 0; background: var(--tml-bg); color: var(--tml-text); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
89
|
+
/* Desktop dashboard: queue on the left, selected record on the right. */
|
|
90
|
+
.tm-index, .tm-index .container,
|
|
91
|
+
.tm-nps-index, .tm-nps-index .container { height: 100vh; height: 100dvh; }
|
|
55
92
|
.tm-index, .tm-nps-index { overflow: hidden; }
|
|
56
|
-
.tm-index .
|
|
57
|
-
|
|
58
|
-
.tm-index .
|
|
59
|
-
.tm-
|
|
60
|
-
|
|
61
|
-
.tm-
|
|
62
|
-
.tm-nps-index .stat-row { flex: 0 0 auto; margin-bottom: 14px; }
|
|
63
|
-
.tm-index .dashboard-shell, .tm-nps-index .dashboard-shell { flex: 1 1 auto; min-height: 0; }
|
|
64
|
-
.tm-index .dashboard-sidebar, .tm-nps-index .dashboard-sidebar { min-height: 0; }
|
|
65
|
-
.tm-index .dashboard-detail, .tm-nps-index .dashboard-detail { min-height: 0; overflow: hidden; }
|
|
66
|
-
.tm-index .record-list, .tm-nps-index .record-list { overflow-y: auto; }
|
|
67
|
-
.tm-index .dashboard-detail .detail-scroll,
|
|
68
|
-
.tm-nps-index .dashboard-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
69
|
-
|
|
93
|
+
.tm-index .container, .tm-nps-index .container { max-width: 1280px; display: flex; flex-direction: column; padding-bottom: 16px; }
|
|
94
|
+
.tm-index .nav, .tm-nps-index .nav,
|
|
95
|
+
.tm-index .container .page-head, .tm-nps-index .container .page-head,
|
|
96
|
+
.tm-nps-index .stat-row { flex: 0 0 auto; }
|
|
97
|
+
.tm-index .container .page-head, .tm-nps-index .container .page-head { margin-bottom: 14px; }
|
|
98
|
+
.tm-nps-index .stat-row { margin-bottom: 14px; }
|
|
70
99
|
/* Standalone show pages keep normal page scrolling. */
|
|
71
100
|
.tm-show { min-height: 100vh; overflow: auto; }
|
|
72
101
|
.tm-show .detail-scroll { overflow: visible; }
|
|
73
102
|
|
|
74
|
-
/*
|
|
75
|
-
|
|
103
|
+
/* Dashboard components — any layout. */
|
|
76
104
|
.tml-dashboard {
|
|
77
|
-
|
|
105
|
+
/* A scoping device, not a layout one: without this the wrapper would
|
|
106
|
+
become the flex/grid child that the content should be. */
|
|
107
|
+
display: contents;
|
|
78
108
|
|
|
79
109
|
& * { box-sizing: border-box; }
|
|
80
110
|
& a { color: var(--tml-accent); text-decoration: none; }
|
|
81
111
|
& a:hover { text-decoration: underline; }
|
|
112
|
+
& .container { max-width: 1020px; margin: 0 auto; padding: 24px 16px 64px; }
|
|
82
113
|
& h1 { font-size: 22px; margin: 0 0 16px; }
|
|
83
114
|
& h1.title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
84
|
-
|
|
85
115
|
/* Index heading row: title on the left, the shareable public link on the right. */
|
|
86
|
-
& .page-head { display: flex; align-items: center; justify-content: space-between;
|
|
87
|
-
gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
|
|
116
|
+
& .page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
|
|
88
117
|
& .page-head h1 { margin: 0; }
|
|
118
|
+
& .nav { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
|
|
119
|
+
& .nav a { padding: 6px 16px; border: 1px solid var(--tml-border); border-radius: 999px; background: var(--tml-surface); color: var(--tml-muted); font-weight: 600; }
|
|
120
|
+
& .nav a:hover { text-decoration: none; color: var(--tml-text); }
|
|
121
|
+
& .nav a.active { background: var(--tml-accent); border-color: var(--tml-accent); color: var(--tml-accent-text); }
|
|
122
|
+
& .nav a.active:hover { color: var(--tml-accent-text); }
|
|
89
123
|
& .share-link { margin-left: auto; font-size: 13px; font-weight: 400; }
|
|
90
|
-
|
|
91
124
|
& .tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--tml-border); margin-bottom: 16px; flex-wrap: wrap; }
|
|
92
125
|
& .tabs a { padding: 8px 14px; border-radius: 8px 8px 0 0; color: var(--tml-muted); }
|
|
93
|
-
& .tabs a.active { color: var(--tml-text); font-weight: 600; border: 1px solid var(--tml-border);
|
|
94
|
-
border-bottom: 2px solid var(--tml-surface); background: var(--tml-surface); margin-bottom: -1px; }
|
|
126
|
+
& .tabs a.active { color: var(--tml-text); font-weight: 600; border: 1px solid var(--tml-border); border-bottom: 2px solid var(--tml-surface); background: var(--tml-surface); margin-bottom: -1px; }
|
|
95
127
|
& .tabs a:hover { text-decoration: none; color: var(--tml-text); }
|
|
96
|
-
& .count { display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 4px; border-radius: 999px;
|
|
97
|
-
background: var(--tml-border); font-size: 12px; text-align: center; }
|
|
98
|
-
|
|
128
|
+
& .count { display: inline-block; min-width: 20px; padding: 0 6px; margin-left: 4px; border-radius: 999px; background: var(--tml-border); font-size: 12px; text-align: center; }
|
|
99
129
|
& .filters { margin-bottom: 16px; display: flex; gap: 8px; align-items: center; }
|
|
100
|
-
& .filters select,
|
|
101
|
-
|
|
130
|
+
& .filters select,
|
|
131
|
+
& .filters input[type=search] { padding: 6px 8px; border: 1px solid var(--tml-border); border-radius: 8px; background: var(--tml-surface); color: var(--tml-text); font: inherit; }
|
|
102
132
|
& .filters input[type=search] { flex: 1; max-width: 320px; }
|
|
103
|
-
|
|
104
133
|
& .card { background: var(--tml-surface); border: 1px solid var(--tml-border); border-radius: 12px; overflow: hidden; }
|
|
105
134
|
& .card.pad { padding: 16px; overflow: visible; }
|
|
106
|
-
|
|
107
135
|
& table { width: 100%; border-collapse: collapse; }
|
|
108
|
-
& th,
|
|
109
|
-
&
|
|
110
|
-
|
|
136
|
+
& th,
|
|
137
|
+
& td { padding: 10px 14px; text-align: left; vertical-align: top; }
|
|
138
|
+
& th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--tml-muted); border-bottom: 1px solid var(--tml-border); }
|
|
111
139
|
& tr + tr td { border-top: 1px solid var(--tml-border); }
|
|
112
|
-
|
|
113
|
-
& .badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
114
|
-
color: #fff; white-space: nowrap; }
|
|
140
|
+
& .badge { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; color: #fff; white-space: nowrap; }
|
|
115
141
|
& .badge.status-pending { background: var(--tml-pending); }
|
|
116
142
|
& .badge.status-approved { background: var(--tml-approved); }
|
|
117
143
|
& .badge.status-archived { background: var(--tml-archived); }
|
|
@@ -119,7 +145,6 @@
|
|
|
119
145
|
& .badge.nps-passive { background: var(--tml-passive); }
|
|
120
146
|
& .badge.nps-detractor { background: var(--tml-detractor); }
|
|
121
147
|
& .badge.locale { background: transparent; color: var(--tml-muted); border: 1px solid var(--tml-border); }
|
|
122
|
-
|
|
123
148
|
& .stars { color: var(--tml-star); letter-spacing: 1px; white-space: nowrap; }
|
|
124
149
|
& .stars .off { color: var(--tml-border); }
|
|
125
150
|
& .muted { color: var(--tml-muted); font-size: 13px; }
|
|
@@ -129,13 +154,11 @@
|
|
|
129
154
|
& td.row-actions { white-space: nowrap; text-align: right; }
|
|
130
155
|
& td.row-actions form { display: inline-block; }
|
|
131
156
|
& td.row-actions form + form { margin-left: 6px; }
|
|
132
|
-
|
|
133
|
-
&
|
|
134
|
-
background: var(--tml-surface); color: var(--tml-text); font: inherit; }
|
|
157
|
+
& button,
|
|
158
|
+
& .button { padding: 8px 14px; border: 1px solid var(--tml-border); border-radius: 8px; cursor: pointer; background: var(--tml-surface); color: var(--tml-text); font: inherit; }
|
|
135
159
|
& button.primary { background: var(--tml-accent); border-color: var(--tml-accent); color: var(--tml-accent-text); }
|
|
136
160
|
& button.danger { color: var(--tml-danger); }
|
|
137
161
|
& button.small { padding: 3px 10px; font-size: 13px; }
|
|
138
|
-
|
|
139
162
|
& .message { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
140
163
|
& dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
141
164
|
& dt { color: var(--tml-muted); }
|
|
@@ -145,15 +168,11 @@
|
|
|
145
168
|
& .empty { padding: 48px 16px; text-align: center; color: var(--tml-muted); }
|
|
146
169
|
& .breadcrumb { margin-bottom: 12px; font-size: 13px; }
|
|
147
170
|
& .case-id { color: var(--tml-muted); font-weight: 400; font-size: 15px; }
|
|
148
|
-
|
|
149
171
|
& .stat-row { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
|
|
150
|
-
& .stat { flex: 1; min-width: 140px; background: var(--tml-surface); border: 1px solid var(--tml-border);
|
|
151
|
-
border-radius: 12px; padding: 14px 16px; }
|
|
172
|
+
& .stat { flex: 1; min-width: 140px; background: var(--tml-surface); border: 1px solid var(--tml-border); border-radius: 12px; padding: 14px 16px; }
|
|
152
173
|
& .stat b { display: block; font-size: 26px; }
|
|
153
174
|
& .stat span { color: var(--tml-muted); font-size: 13px; }
|
|
154
|
-
|
|
155
|
-
/* NPS score card: the number, the band it falls in, and where it sits on the
|
|
156
|
-
−100..100 scale — so nobody has to google whether 50 is any good. */
|
|
175
|
+
/* NPS score card: the number, the band it falls in, and where it sits on the −100..100 scale — so nobody has to google whether 50 is any good. */
|
|
157
176
|
& .stat-nps { flex: 2.4; min-width: 240px; }
|
|
158
177
|
& .stat-headline { display: flex; align-items: center; gap: 10px; }
|
|
159
178
|
& .stat-headline b { display: inline-block; }
|
|
@@ -170,10 +189,8 @@
|
|
|
170
189
|
& .nps-seg.nps-band-good { flex: 30; background: var(--tml-nps-good); }
|
|
171
190
|
& .nps-seg.nps-band-great { flex: 40; background: var(--tml-nps-great); }
|
|
172
191
|
& .nps-seg.nps-band-excellent { flex: 30; background: var(--tml-nps-excellent); }
|
|
173
|
-
/* Hidden until dashboard.js reads data-nps-marker and positions it, so a
|
|
174
|
-
|
|
175
|
-
& .nps-marker { display: none; position: absolute; top: -3px; width: 4px; height: 12px; margin-left: -2px;
|
|
176
|
-
border-radius: 2px; background: var(--tml-text); box-shadow: 0 0 0 2px var(--tml-surface); }
|
|
192
|
+
/* Hidden until dashboard.js reads data-nps-marker and positions it, so a score of 50 never renders as if it were −100. */
|
|
193
|
+
& .nps-marker { display: none; position: absolute; top: -3px; width: 4px; height: 12px; margin-left: -2px; border-radius: 2px; background: var(--tml-text); box-shadow: 0 0 0 2px var(--tml-surface); }
|
|
177
194
|
& .nps-marker.placed { display: block; }
|
|
178
195
|
& .nps-meter-ticks { position: relative; height: 14px; margin-top: 3px; color: var(--tml-muted); font-size: 11px; }
|
|
179
196
|
& .nps-meter-ticks span { position: absolute; transform: translateX(-50%); }
|
|
@@ -183,113 +200,58 @@
|
|
|
183
200
|
& .nps-meter-ticks .tick-great { left: 85%; }
|
|
184
201
|
& .nps-meter-ticks .tick-max { left: 100%; transform: translateX(-100%); }
|
|
185
202
|
& .nps-sample { margin: 8px 0 0; font-size: 12px; }
|
|
186
|
-
|
|
187
203
|
/* The formula and the benchmarks, tucked behind a disclosure. */
|
|
188
|
-
& .nps-guide { flex: 0 0 auto; margin-bottom: 14px; background: var(--tml-surface);
|
|
189
|
-
border: 1px solid var(--tml-border); border-radius: 12px; }
|
|
204
|
+
& .nps-guide { flex: 0 0 auto; margin-bottom: 14px; background: var(--tml-surface); border: 1px solid var(--tml-border); border-radius: 12px; }
|
|
190
205
|
& .nps-guide summary { padding: 10px 16px; color: var(--tml-muted); font-size: 13px; cursor: pointer; }
|
|
191
206
|
& .nps-guide summary:hover { color: var(--tml-text); }
|
|
192
207
|
& .nps-guide[open] summary { border-bottom: 1px solid var(--tml-border); }
|
|
193
208
|
& .nps-guide-body { padding: 12px 16px 14px; font-size: 13px; }
|
|
194
209
|
& .nps-guide-body p { margin: 0; max-width: 78ch; }
|
|
195
|
-
& .nps-bands { display: grid; grid-template-columns: max-content max-content 1fr; gap: 8px 14px;
|
|
196
|
-
margin: 12px 0; padding: 0; list-style: none; align-items: baseline; }
|
|
210
|
+
& .nps-bands { display: grid; grid-template-columns: max-content max-content 1fr; gap: 8px 14px; margin: 12px 0; padding: 0; list-style: none; align-items: baseline; }
|
|
197
211
|
& .nps-bands li { display: contents; }
|
|
198
212
|
& .nps-bands b { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
199
|
-
|
|
200
|
-
& .best-line-form textarea { width: 100%; padding: 8px; border: 1px solid var(--tml-border); border-radius: 8px;
|
|
201
|
-
background: var(--tml-surface); color: var(--tml-text); font: inherit; resize: vertical; }
|
|
213
|
+
& .best-line-form textarea { width: 100%; padding: 8px; border: 1px solid var(--tml-border); border-radius: 8px; background: var(--tml-surface); color: var(--tml-text); font: inherit; resize: vertical; }
|
|
202
214
|
& .best-line-hint { margin: 2px 0 6px; }
|
|
203
215
|
& .best-line-save { margin-top: 8px; }
|
|
204
216
|
& .panel-head { flex: 0 0 auto; }
|
|
205
217
|
& .panel-head h1 { margin-bottom: 12px; }
|
|
206
218
|
& .detail-scroll { min-height: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
207
|
-
& .testimonial-panel,
|
|
219
|
+
& .testimonial-panel,
|
|
220
|
+
& .nps-panel { min-width: 0; display: flex; flex-direction: column; }
|
|
208
221
|
& .testimonial-panel .actions { flex: 0 0 auto; }
|
|
209
222
|
& .testimonial-panel .detail-scroll > .actions { margin: 0; }
|
|
210
|
-
|
|
211
|
-
& .dashboard-
|
|
212
|
-
& .dashboard-sidebar {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
& .dashboard-sidebar .tabs {
|
|
216
|
-
|
|
217
|
-
& .dashboard-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center;
|
|
218
|
-
justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px;
|
|
219
|
-
border-radius: 8px; color: var(--tml-muted); font-weight: 600; }
|
|
220
|
-
& .dashboard-sidebar .tabs a.active { color: var(--tml-text); border: 0; background: transparent;
|
|
221
|
-
box-shadow: none; margin: 0; }
|
|
222
|
-
& .dashboard-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0;
|
|
223
|
-
height: 2px; border-radius: 999px; background: var(--tml-accent); }
|
|
224
|
-
& .dashboard-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--tml-text);
|
|
225
|
-
background: var(--tml-bg); }
|
|
223
|
+
& .dashboard-shell { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(330px, 430px) 1fr; gap: 16px; }
|
|
224
|
+
& .dashboard-sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--tml-surface); border: 1px solid var(--tml-border); border-radius: 8px; overflow: hidden; }
|
|
225
|
+
& .dashboard-sidebar .tabs { flex: 0 0 auto; margin: 12px 12px 0; padding: 0; background: transparent; border: 0; border-radius: 0; gap: 6px; flex-wrap: nowrap; }
|
|
226
|
+
& .dashboard-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px; border-radius: 8px; color: var(--tml-muted); font-weight: 600; }
|
|
227
|
+
& .dashboard-sidebar .tabs a.active { color: var(--tml-text); border: 0; background: transparent; box-shadow: none; margin: 0; }
|
|
228
|
+
& .dashboard-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0; height: 2px; border-radius: 999px; background: var(--tml-accent); }
|
|
229
|
+
& .dashboard-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--tml-text); background: var(--tml-bg); }
|
|
226
230
|
& .dashboard-sidebar .tabs .count { margin-left: 0; background: color-mix(in srgb, var(--tml-muted) 14%, transparent); }
|
|
227
231
|
& .dashboard-sidebar .tabs a.active .count { background: var(--tml-border); }
|
|
228
|
-
& .dashboard-sidebar .filters { flex: 0 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) auto;
|
|
229
|
-
margin: 0; padding: 12px; border-bottom: 1px solid var(--tml-border); }
|
|
232
|
+
& .dashboard-sidebar .filters { flex: 0 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) auto; margin: 0; padding: 12px; border-bottom: 1px solid var(--tml-border); }
|
|
230
233
|
& .dashboard-sidebar .filters select { grid-column: 1 / -1; width: 100%; }
|
|
231
234
|
& .dashboard-sidebar .filters input[type=search] { min-width: 0; max-width: none; }
|
|
232
|
-
& .record-list { flex: 1 1 auto; min-height: 0; }
|
|
233
|
-
& .record-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px;
|
|
234
|
-
color: var(--tml-text); border-bottom: 1px solid var(--tml-border); }
|
|
235
|
+
& .record-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
236
|
+
& .record-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px; color: var(--tml-text); border-bottom: 1px solid var(--tml-border); }
|
|
235
237
|
& .record-row:hover { text-decoration: none; background: var(--tml-bg); }
|
|
236
|
-
& .record-row.active { background: color-mix(in srgb, var(--tml-accent) 9%, var(--tml-surface));
|
|
237
|
-
box-shadow: inset 3px 0 0 var(--tml-accent); }
|
|
238
|
+
& .record-row.active { background: color-mix(in srgb, var(--tml-accent) 9%, var(--tml-surface)); box-shadow: inset 3px 0 0 var(--tml-accent); }
|
|
238
239
|
& .record-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
239
|
-
& .record-topline,
|
|
240
|
-
& .record-
|
|
240
|
+
& .record-topline,
|
|
241
|
+
& .record-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
242
|
+
& .record-copy,
|
|
243
|
+
& .record-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
241
244
|
& .record-copy { display: block; font-weight: 600; }
|
|
242
245
|
& .record-time { margin-left: auto; white-space: nowrap; }
|
|
243
|
-
& .record-side { display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between;
|
|
244
|
-
gap: 8px; padding-top: 1px; }
|
|
246
|
+
& .record-side { display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; gap: 8px; padding-top: 1px; }
|
|
245
247
|
& .dashboard-sidebar .pager { flex: 0 0 auto; margin: 0; padding: 10px 12px; border-top: 1px solid var(--tml-border); }
|
|
246
|
-
& .dashboard-detail { min-width: 0; display: flex; flex-direction: column; }
|
|
248
|
+
& .dashboard-detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
247
249
|
& .dashboard-detail .testimonial-panel,
|
|
248
250
|
& .dashboard-detail .nps-panel { flex: 1 1 auto; min-height: 0; }
|
|
251
|
+
& .dashboard-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
249
252
|
& .dashboard-detail .actions { margin-bottom: 0; }
|
|
250
253
|
& .empty-state { margin: auto; max-width: 320px; padding: 24px; text-align: center; }
|
|
251
254
|
& .empty-state h2 { margin: 0 0 6px; font-size: 18px; }
|
|
252
255
|
& .empty-state p { margin: 0; }
|
|
253
256
|
& .mobile-back { display: none; }
|
|
254
257
|
}
|
|
255
|
-
|
|
256
|
-
@media (max-width: 760px) {
|
|
257
|
-
.tm-index, .tm-nps-index, .tm-show { font-size: 14px; }
|
|
258
|
-
.tml-page { padding: 14px 10px 24px; }
|
|
259
|
-
.tm-index, .tm-nps-index, .tm-show { overflow-x: hidden; }
|
|
260
|
-
.tm-index, .tm-nps-index { overflow-y: auto; overflow-x: hidden; }
|
|
261
|
-
.tm-index, .tm-index .tml-page,
|
|
262
|
-
.tm-nps-index, .tm-nps-index .tml-page { min-height: 100vh; height: auto; }
|
|
263
|
-
.tm-index .tml-page, .tm-nps-index .tml-page { padding-bottom: 10px; }
|
|
264
|
-
.tm-index .dashboard-shell, .tm-nps-index .dashboard-shell { min-height: calc(100vh - 76px); }
|
|
265
|
-
.tm-index .dashboard-sidebar, .tm-index .dashboard-detail,
|
|
266
|
-
.tm-nps-index .dashboard-sidebar, .tm-nps-index .dashboard-detail { min-height: calc(100vh - 76px); }
|
|
267
|
-
.tm-index .dashboard-detail, .tm-nps-index .dashboard-detail { overflow: visible; }
|
|
268
|
-
.tm-index .record-list, .tm-nps-index .record-list { overflow: visible; }
|
|
269
|
-
.tm-index .dashboard-detail .testimonial-panel,
|
|
270
|
-
.tm-nps-index .dashboard-detail .nps-panel { min-height: calc(100vh - 64px); }
|
|
271
|
-
|
|
272
|
-
.tml-dashboard {
|
|
273
|
-
& .dashboard-detail, & .testimonial-panel, & .nps-panel, & .detail-scroll, & .card, & dl, & dd,
|
|
274
|
-
& .best-line-form, & .best-line-form textarea { min-width: 0; max-width: 100%; }
|
|
275
|
-
& .testimonial-panel .card, & .nps-panel .card { width: calc(100vw - 20px); }
|
|
276
|
-
& .best-line-form textarea { width: 100%; }
|
|
277
|
-
& .message, & .best-line-hint, & dd, & .muted { overflow-wrap: anywhere; max-width: calc(100vw - 54px); }
|
|
278
|
-
& .dashboard-shell { display: block; }
|
|
279
|
-
& .dashboard-shell.has-selected .dashboard-sidebar { display: none; }
|
|
280
|
-
& .dashboard-shell:not(.has-selected) .dashboard-detail { display: none; }
|
|
281
|
-
& .record-row { padding: 12px; }
|
|
282
|
-
& .record-meta { display: none; }
|
|
283
|
-
& .mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
284
|
-
& .panel-head h1 { font-size: 18px; }
|
|
285
|
-
& dl { grid-template-columns: 1fr; gap: 3px; }
|
|
286
|
-
& .stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
287
|
-
& .stat { min-width: 0; padding: 10px 12px; }
|
|
288
|
-
& .stat b { font-size: 22px; }
|
|
289
|
-
& .stat-nps { grid-column: 1 / -1; }
|
|
290
|
-
& .nps-bands { grid-template-columns: max-content 1fr; }
|
|
291
|
-
& .nps-bands b { grid-column: 2; }
|
|
292
|
-
& .nps-bands li > .muted { grid-column: 1 / -1; }
|
|
293
|
-
& .share-link { flex: 1 0 100%; margin-left: 0; text-align: center; }
|
|
294
|
-
}
|
|
295
|
-
}
|
data/lib/testimonials/version.rb
CHANGED