x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="flex items-center gap" data-controller="upload-preview" data-upload-preview-default-image-value="https://csszero.lazaronixon.com/assets/default-picture-956f249d.webp">
<label class="btn btn--icon">
<input type="file" name="avatar" id="avatar" class="sr-only" accept="image/*" data-upload-preview-target="input" data-action="upload-preview#previewImage" />
<img aria-hidden="true" src="/assets/camera-84102100.svg" width="20" height="20" />
<span class="sr-only">Upload avatar</span>
</label>
<div class="avatar" style="--avatar-size: 100px;">
<img alt="Avatar" data-upload-preview-target="image" src="/assets/cartoon-93de1535.jpg" width="48" height="48" />
</div>
<label class="btn btn--icon btn--negative" data-action="click->upload-preview#clear">
<input type="hidden" name="remove_avatar" id="remove_avatar" value="false" data-upload-preview-target="removeInput" autocomplete="off" />
<img aria-hidden="true" src="/assets/minus-b71b2ac1.svg" width="20" height="20" />
<span class="sr-only">Remove avatar</span>
</label>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%= tag.div class: "flex items-center gap", data: { controller: "upload-preview", upload_preview_default_image_value: asset_url("default-picture.webp") } do %>
<label class="btn btn--icon">
<%= file_field_tag :avatar, class: "sr-only", accept: "image/*", data: { upload_preview_target: "input", action: "upload-preview#previewImage" } %>
<%= image_tag "camera.svg", size: 20, aria: { hidden: true } %>
<span class="sr-only">Upload avatar</span>
</label>
<div class="avatar" style="--avatar-size: 100px;">
<%= image_tag "cartoon.jpg", size: 48, alt: "Avatar", data: { upload_preview_target: "image" } %>
</div>
<label class="btn btn--icon btn--negative" data-action="click->upload-preview#clear">
<%= hidden_field_tag "remove_avatar", false, data: { upload_preview_target: "removeInput" } %>
<%= image_tag "minus.svg", size: 20, aria: { hidden: true } %>
<span class="sr-only">Remove avatar</span>
</label>
<% end %>
CSS is not required or multiple files are needed, check the notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "image", "input", "removeInput" ]
static values = { defaultImage: String }
previewImage() {
const selectedFile = this.inputTarget.files[0]
if (selectedFile) {
this.imageTarget.src = URL.createObjectURL(selectedFile);
this.imageTarget.onload = () => { URL.revokeObjectURL(this.imageTarget.src) }
this.removeInputTarget.value = false
}
}
clear() {
this.imageTarget.src = this.defaultImageValue
this.removeInputTarget.value = true
}
}