Skip to content

Commit 5e6e65c

Browse files
committed
improve share modal
1 parent 2977126 commit 5e6e65c

File tree

4 files changed

+111
-16
lines changed

4 files changed

+111
-16
lines changed

app/assets/stylesheets/modals.css

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,26 @@
152152
.modal-pull-fade {
153153
opacity: 0.4 !important;
154154
}
155+
156+
/* Larger touch targets on mobile */
157+
.copy-link {
158+
min-width: 44px;
159+
min-height: 44px;
160+
display: inline-flex;
161+
align-items: center;
162+
justify-content: center;
163+
padding: 0.5rem;
164+
}
165+
166+
.copy-link i {
167+
font-size: 1.2rem;
168+
}
169+
170+
/* Touch feedback */
171+
.copy-link:active {
172+
background-color: rgb(0 0 0 / 8%);
173+
border-radius: 0.4rem;
174+
}
155175
}
156176

157177
.modal-close-button {
@@ -176,18 +196,40 @@
176196

177197
/* Share modal */
178198

179-
#share-edit-link, #share-view-link {
199+
#share-ownership-link, #share-edit-link, #share-view-link {
180200
min-height: 2.5rem;
181201
}
182202

183-
#share-edit-link a, #share-view-link a {
203+
#share-ownership-link a, #share-edit-link a, #share-view-link a {
184204
display: flex;
185205
align-items: center;
186206
justify-content: center;
187207
width: 100%;
188208
height: 100%;
189209
}
190210

211+
/* Permission badges */
212+
.permission-badge {
213+
font-size: 0.7rem;
214+
font-weight: 600;
215+
padding: 0.15rem 0.5rem;
216+
border-radius: 0.4rem;
217+
text-transform: uppercase;
218+
letter-spacing: 0.3px;
219+
white-space: nowrap;
220+
}
221+
222+
.badge-owner, .badge-edit, .badge-view {
223+
background-color: rgb(255 255 255 / 30%);
224+
color: #fff;
225+
}
226+
227+
@media (width <= 574px) {
228+
.permission-badge {
229+
display: none;
230+
}
231+
}
232+
191233
#share-modal .form-floating > .form-select {
192234
height: unset;
193235
}
@@ -233,6 +275,22 @@
233275
color: var(--bs-secondary-color) !important;
234276
}
235277

278+
.copy-success {
279+
color: var(--color-dark-moss-green) !important;
280+
transition: all 0.2s ease-in-out;
281+
animation: copy-pulse 0.4s ease-in-out;
282+
}
283+
284+
@keyframes copy-pulse {
285+
0%, 100% {
286+
transform: scale(1);
287+
}
288+
289+
50% {
290+
transform: scale(1.2);
291+
}
292+
}
293+
236294
#share-ownership-link .map-avatar {
237295
border-radius: 50%;
238296
display: inline-block;

app/javascript/controllers/map/modal_controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { draw } from 'maplibre/edit'
55
export default class extends Controller {
66
close() {
77
resetControls()
8-
if (draw) {
8+
if (draw) {
99
draw.changeMode('simple_select', { featureIds: [] })
1010
map.fire('draw.modechange')
1111
}
12-
// TODO: drop anchor if present
12+
// TODO: drop anchor if present
1313
window.history.pushState({}, '', `${window.location.pathname}`)
1414
}
1515
}

app/javascript/controllers/map/share_controller.js

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@ export default class extends Controller {
1515

1616
// Update share icons for native sharing support
1717
if (navigator.share) {
18+
const ownershipLinkIcon = document.querySelector('#share-ownership-link i')
19+
if (ownershipLinkIcon) {
20+
ownershipLinkIcon.classList.remove('bi-shield-fill-check')
21+
ownershipLinkIcon.classList.add('bi-share')
22+
}
23+
1824
const editLinkIcon = document.querySelector('#share-edit-link i')
1925
if (editLinkIcon) {
20-
editLinkIcon.classList.remove('bi-link-45deg')
26+
editLinkIcon.classList.remove('bi-pencil-square')
2127
editLinkIcon.classList.add('bi-share')
2228
}
2329

2430
const viewLinkIcon = document.querySelector('#share-view-link i')
2531
if (viewLinkIcon) {
26-
viewLinkIcon.classList.remove('bi-link-45deg')
32+
viewLinkIcon.classList.remove('bi-eye-fill')
2733
viewLinkIcon.classList.add('bi-share')
2834
}
2935
}
@@ -41,36 +47,39 @@ export default class extends Controller {
4147
copyOwnershipLink (e) {
4248
e.preventDefault()
4349
const ownershipLink = window.location.origin + document.querySelector('#share-ownership-link a').getAttribute('href')
44-
this.copyToClipboard(ownershipLink, "Ownership link copied")
50+
this.copyToClipboard(ownershipLink, "Ownership link copied", e.currentTarget)
4551
}
4652

4753
copyEditLink (e) {
4854
e.preventDefault()
4955
const editLink = window.location.origin + document.querySelector('#share-edit-link a').getAttribute('href')
50-
this.copyToClipboard(editLink, "Edit link copied")
56+
this.copyToClipboard(editLink, "Edit link copied", e.currentTarget)
5157
}
5258

5359
copyViewLink (e) {
5460
e.preventDefault()
5561
const viewLink = window.location.origin + document.querySelector('#share-view-link a').getAttribute('href')
56-
this.copyToClipboard(viewLink, "View link copied")
62+
this.copyToClipboard(viewLink, "View link copied", e.currentTarget)
5763
}
5864

59-
copyToClipboard (text, successMessage) {
65+
copyToClipboard (text, successMessage, iconElement = null) {
6066
// Try modern clipboard API first
6167
if (navigator.clipboard && navigator.clipboard.writeText) {
6268
navigator.clipboard.writeText(text).then(() => {
6369
status(successMessage)
70+
if (iconElement) {
71+
this.showCopySuccess(iconElement)
72+
}
6473
}).catch(() => {
65-
this.fallbackCopyToClipboard(text, successMessage)
74+
this.fallbackCopyToClipboard(text, successMessage, iconElement)
6675
})
6776
} else {
6877
// Fallback for non-secure contexts
69-
this.fallbackCopyToClipboard(text, successMessage)
78+
this.fallbackCopyToClipboard(text, successMessage, iconElement)
7079
}
7180
}
7281

73-
fallbackCopyToClipboard (text, successMessage) {
82+
fallbackCopyToClipboard (text, successMessage, iconElement = null) {
7483
const textArea = document.createElement('textarea')
7584
textArea.value = text
7685
textArea.style.position = 'fixed'
@@ -83,6 +92,9 @@ export default class extends Controller {
8392
try {
8493
document.execCommand('copy')
8594
status(successMessage)
95+
if (iconElement) {
96+
this.showCopySuccess(iconElement)
97+
}
8698
} catch (err) {
8799
status("Failed to copy link")
88100
console.error('Fallback copy failed:', err)
@@ -91,6 +103,27 @@ export default class extends Controller {
91103
document.body.removeChild(textArea)
92104
}
93105

106+
showCopySuccess (iconElement) {
107+
// Find the actual icon element
108+
const icon = iconElement.querySelector('i') || iconElement
109+
110+
// Store original classes
111+
const wasCheck = icon.classList.contains('bi-check2')
112+
113+
// Don't animate if already showing success
114+
if (wasCheck) return
115+
116+
// Change to checkmark with success styling
117+
icon.classList.remove('bi-copy')
118+
icon.classList.add('bi-check2', 'copy-success')
119+
120+
// Revert after 2 seconds
121+
setTimeout(() => {
122+
icon.classList.remove('bi-check2', 'copy-success')
123+
icon.classList.add('bi-copy')
124+
}, 2000)
125+
}
126+
94127
nativeShareOwnershipLink (e) {
95128
if (navigator.share) {
96129
e.preventDefault()
@@ -127,6 +160,6 @@ export default class extends Controller {
127160
copyEmbedCode (e) {
128161
e.preventDefault()
129162
const embedCode = document.querySelector('#embed-code').value
130-
this.copyToClipboard(embedCode, "Embed code copied")
163+
this.copyToClipboard(embedCode, "Embed code copied", e.currentTarget)
131164
}
132165
}

app/views/maps/modals/_share.haml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
- @map.owners.each do |owner|
2020
- if owner.image
2121
= image_tag avatar_url(owner.image, 24), class: 'map-avatar', style: 'width: 24px; height: 24px; margin-right: 4px;', crossorigin: "anonymous", title: owner.name || owner.email, data: { "toggle": 'tooltip', 'bs-placement': 'top' }
22+
%i.bi.bi-shield-fill-check.me-2
2223
Share ownership link
24+
%span.permission-badge.badge-owner.ms-2 Full Control
2325
%a.link.text-muted.copy-link{ data: { action: "click->map--share#copyOwnershipLink", "toggle": 'tooltip', 'bs-placement': 'top' }, title: "Copy link to clipboard" }
2426
%i.bi.bi-copy.me-1
2527
%small.text-muted.mt-2.flex-center.center
@@ -30,8 +32,9 @@
3032
.d-flex.gap-2.align-items-center
3133
%button#share-edit-link.form-button.btn.btn-blue.flex-grow-1{ type: "button" }
3234
=link_to(map_path(@map.private_id), data: { turbo: false, action: "click->map--share#nativeShareEditLink" }) do
33-
%i.bi.bi-link-45deg.me-2
35+
%i.bi.bi-pencil-square.me-2
3436
Share edit link
37+
%span.permission-badge.badge-edit.ms-2 Can Edit
3538
%a.link.text-muted.copy-link{ data: { action: "click->map--share#copyEditLink", "toggle": 'tooltip', 'bs-placement': 'top' }, title: "Copy link to clipboard" }
3639
%i.bi.bi-copy.me-1
3740
%small.text-muted.mt-2.flex-center.center
@@ -42,8 +45,9 @@
4245
.d-flex.gap-2.align-items-center
4346
%button#share-view-link.form-button.btn.btn-blue.flex-grow-1{ type: "button" }
4447
=link_to(map_path(id: @map.public_id), data: { turbo: false, action: "click->map--share#nativeShareViewLink" }) do
45-
%i.bi.bi-link-45deg.me-1
48+
%i.bi.bi-eye-fill.me-2
4649
Share view link
50+
%span.permission-badge.badge-view.ms-2 Read Only
4751
%a.link.text-muted.copy-link{ data: { action: "click->map--share#copyViewLink", "toggle": 'tooltip', 'bs-placement': 'top' }, title: "Copy link to clipboard" }
4852
%i.bi.bi-copy.me-1
4953
%small.text-muted.mt-2.flex-center.center

0 commit comments

Comments
 (0)