Skip to content

feat: Accessibility for project detail page and file browser #206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions application/app/assets/stylesheets/file_browser.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
background-color: #f8f9fa;
}

.file-row:focus {
outline: none;
background-color: #e9ecef; /* Bootstrap light gray */
}

.drop-overlay {
background-color: rgba(13, 110, 253, 0.08);
backdrop-filter: blur(3px);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export default class extends Controller {
}
}

handleKeydown(event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.handleDoubleClick(event); // Reuse the existing logic
}
}

handleDragStart(event) {
const path = event.currentTarget.dataset.entryPath
event.dataTransfer.setData("text/plain", path)
Expand Down
77 changes: 50 additions & 27 deletions application/app/views/file_browser/_browser.html.erb
Original file line number Diff line number Diff line change
@@ -1,53 +1,74 @@
<div class="card mb-3 overflow-hidden shadow-sm">
<h3 class="visually-hidden"><%= t('.header_file_browser_text') %></h3>
<div class="card-header d-flex justify-content-between align-items-center">
<!-- Path display (left) -->
<div data-file-browser-target="pathDisplay" class="d-flex align-items-center flex-wrap gap-2">
<% path_parts = current_path.split('/').reject(&:blank?) %>
<% accumulated_path = '/' %>
<% path_parts.each do |part| %>
<% accumulated_path = File.join(accumulated_path, part) %>
<span class="text-muted">/</span>
<span class="breadcrumb-folder"
data-entry-path="<%= accumulated_path %>"
data-entry-type="folder"
data-action="dblclick->file-browser#handleDoubleClick">
<i class="bi bi-folder-fill"></i> <%= part %>
</span>
<% end %>
</div>
<nav aria-label="<%= t('.nav_file_browser_label') %>">
<ol data-file-browser-target="pathDisplay" class="breadcrumb d-flex align-items-center flex-wrap gap-2">
<% path_parts = current_path.split('/').reject(&:blank?) %>
<% accumulated_path = '/' %>
<% path_parts.each do |part| %>
<li>
<% accumulated_path = File.join(accumulated_path, part) %>
<span class="text-muted">/</span>
<a class="breadcrumb-folder text-body text-decoration-none"
tabindex="0"
data-entry-path="<%= accumulated_path %>"
data-entry-type="folder"
data-action="dblclick->file-browser#handleDoubleClick keydown->file-browser#handleKeydown">
<i class="bi bi-folder-fill" aria-hidden="true"></i> <%= part %>
</a>
</li>
<% end %>
</ol>
</nav>
Comment on lines +5 to +23
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path browser redefined as a ul list with li items inside a nav tag, to make it more understandable for accessibility devices. tabindex="0" and the keydown handler in data-action allow the usage of keyboard


<!-- Path editor (hidden initially) -->
<div data-file-browser-target="pathEditor" class="flex-grow-1 d-flex align-items-center gap-2 d-none" style="max-width: 75%;">
<input type="text"
class="form-control form-control-sm flex-grow-1"
value="<%= current_path %>"
aria-label="<%= t('.input_path_editor_label') %>"
data-file-browser-target="pathInput">
<button class="btn btn-sm btn-outline-primary" type="button"
title="<%= t('.button_confirm_path_title') %>"
aria-label="<%= t('.button_confirm_path_label') %>"
data-action="click->file-browser#navigate">
<i class="bi bi-check-lg"></i>
<i class="bi bi-check-lg" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_confirm_path_text') %></span>
</button>
<button class="btn btn-sm btn-outline-danger" type="button"
title="<%= t('.button_cancel_edit_path_title') %>"
aria-label="<%= t('.button_cancel_edit_path_label') %>"
data-action="click->file-browser#cancelEditPath">
<i class="bi bi-x-lg"></i>
<i class="bi bi-x-lg" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_cancel_edit_path_text') %></span>
</button>
</div>

<!-- Actions (right) -->
<div>
<button class="btn btn-sm btn-outline-dark"
title="<%= t('.edit_path') %>"
title="<%= t('.button_edit_path_title') %>"
aria-label="<%= t('.button_edit_path_label') %>"
data-action="click->file-browser#editPath">
<i class="bi bi-pencil"></i>
<i class="bi bi-pencil" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_edit_path_text') %></span>
</button>
<button class="btn btn-sm btn-outline-dark"
data-entry-path="<%= Dir.home %>"
data-entry-type="folder"
data-action="click->file-browser#handleDoubleClick">
<i class="bi bi-house-door-fill"></i>
data-entry-path="<%= Dir.home %>"
data-entry-type="folder"
title="<%= t('.button_home_title') %>"
aria-label="<%= t('.button_home_label') %>"
data-action="click->file-browser#handleDoubleClick">
<i class="bi bi-house-door-fill" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_home_text') %></span>
</button>
<button class="btn btn-sm btn-outline-dark"
title="<%= t('.button_close_browser_title') %>"
aria-label="<%= t('.button_close_browser_label') %>"
data-action="click->file-browser#hideContainer">
<i class="bi bi-x-lg"></i>
<i class="bi bi-x-lg" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_close_browser_text') %></span>
</button>

<%= render partial: '/shared/ood_folder_button', locals: { path: current_path }%>
Expand All @@ -60,9 +81,10 @@
<li class="list-group-item d-flex align-items-center gap-3 file-row text-muted"
data-entry-path="<%= File.expand_path('..', current_path) %>"
data-entry-type="folder"
data-action="dblclick->file-browser#handleDoubleClick">
tabindex="0"
data-action="dblclick->file-browser#handleDoubleClick keydown->file-browser#handleKeydown">
<i class="bi bi-arrow-up fs-6"></i>
<span>.. (<%= t('.parent_folder') %>)</span>
<span>.. (<%= t('.browser_parent_folder_text') %>)</span>
</li>
<% end %>

Expand All @@ -71,10 +93,11 @@
data-entry-path="<%= entry.path %>"
data-entry-type="<%= entry.type %>"
draggable="true"
data-action="dragstart->file-browser#handleDragStart dragend->file-browser#handleDragEnd dblclick->file-browser#handleDoubleClick">
tabindex="0"
data-action="dragstart->file-browser#handleDragStart dragend->file-browser#handleDragEnd dblclick->file-browser#handleDoubleClick keydown->file-browser#handleKeydown">
Comment on lines +96 to +97
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these lines allow the use of keyboard


<!-- Icon -->
<i class="bi <%= entry[:type] == 'folder' ? 'bi-folder-fill' : 'bi-file-earmark' %> fs-5 text-secondary"></i>
<i class="bi <%= entry[:type] == 'folder' ? 'bi-folder-fill' : 'bi-file-earmark' %> fs-5 text-secondary" aria-hidden="true"></i>

<!-- Name -->
<span class="flex-grow-1"><%= entry.name %></span>
Expand Down
3 changes: 2 additions & 1 deletion application/app/views/file_browser/_file_drop.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<%= t('.file_drop_text') %>
</div>
<div data-file-drop-target="icon" class="position-absolute translate-middle-y drop-text d-none" style="top: 50%; left: 1rem; z-index: 100;">
<i class="bi bi-plus-square-fill fs-1"></i>
<i class="bi bi-plus-square-fill fs-1" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.icon_drop_label') %></span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need a title or hidden span here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it

<div data-file-drop-target="feedback" class="position-absolute top-50 start-50 translate-middle text-center drop-text fs-4 d-none" style="z-index: 100;">
</div>
Expand Down
3 changes: 2 additions & 1 deletion application/app/views/projects/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<% content_for :title, t('.page_title') %>
<div class="project-details container-md content" role="main">
<%= render partial: '/shared/breadcrumbs', locals: { links: [{text: t('.projects'), url: projects_path}, {text: @project.name}]} %>
<%= render partial: '/shared/breadcrumbs', locals: { links: [{text: t('.breadcrumbs_text'), url: projects_path}, {text: @project.name}]} %>

<div class="row mb-2">
<div class="col-md-12">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
<a href="<%= files_app_url(project.download_dir) %>"
target="_blank"
class="btn btn-sm btn-outline-secondary"
title="<%= t('.open_downloads_folder') %>">
<i class="bi bi-folder-fill"></i>
aria-label="<%= t('.button_open_downloads_folder_title') %>"
title="<%= t('.button_open_downloads_folder_title') %>">
<i class="bi bi-folder-fill" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_open_downloads_folder_title') %></span>
</a>
<div class="d-flex flex-column">
<h5 class="mb-0"><%= t('.download_files') %></h5>
<h3 class="mb-0 h5"><%= t('.header_download_files_text') %></h3>
<small class="text-muted"><%= project.download_dir %></small>
</div>
</div>
Expand All @@ -22,5 +24,4 @@
</div>

</div>

</div>
20 changes: 10 additions & 10 deletions application/app/views/projects/show/_download_files.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
<div id="<%= tab_anchor_for(project) %>" class="tab-pane fade show active" role="tabpanel" aria-labelledby="<%= tab_label_for(project) %>">
<%= render partial: '/projects/show/download_actions', locals: { project: project } %>

<div class="container files rounded border">
<ul class="container files rounded border">
<% if files.empty? %>
<div class="row py-2 justify-content-center custom-stripe">
<%= t('.download_files_empty') %>
<%= t('.message_download_files_empty_text') %>
</div>
<% end %>
<% files.each_with_index do |file, index| %>
<div class="row py-2 align-items-center <%= 'custom-stripe' if index.even? %>">
<li class="row py-2 align-items-center <%= 'custom-stripe' if index.even? %>">
<div class="col-md-10">
<div class="d-flex align-items-center">
<%= render partial: '/shared/file_row_date', locals: { date: file.creation_date, title: t('.schedule_date'), classes: 'mx-2'} %>
<%= render partial: '/shared/file_row_date', locals: { date: file.creation_date, title: t('.field_schedule_date_title'), classes: 'mx-2'} %>

<%= render partial: 'shared/repo_badge', locals: {
repo_url: file.connector_metadata.files_url,
icon_html: connector_icon(file.type),
name: file.connector_metadata.repo_name,
tooltip: t('.repository_files')
tooltip: t('.badge_repository_files_tooltip')
} %>

<span class="me-2"><%= file.filename %></span>
Expand All @@ -35,13 +35,13 @@
<% end %>
</div>

<%= render partial: '/shared/file_row_date', locals: { date: file.end_date, title: t('.completion_date'), classes: 'mx-2'} if file.end_date %>
<%= render partial: '/shared/file_row_date', locals: { date: file.start_date, title: t('.download_start_date'), classes: 'mx-2'} if file.status.downloading? %>
<%= render partial: '/shared/file_row_date', locals: { date: file.end_date, title: t('.field_completion_date_title'), classes: 'mx-2'} if file.end_date %>
<%= render partial: '/shared/file_row_date', locals: { date: file.start_date, title: t('.field_download_start_date_title'), classes: 'mx-2'} if file.status.downloading? %>

<%= render layout: 'shared/button_to', locals: {
url: project_download_file_path(project_id: project.id, id: file.id),
method: 'DELETE',
title: t('.delete_file'),
title: t('.button_delete_file_title'),
class: 'btn-sm btn-outline-secondary',
icon: 'bi bi-trash',
modal_id: 'modal-delete-confirmation',
Expand All @@ -53,7 +53,7 @@
<% end %>

</div>
</div>
</li>
<% end %>
</div>
</ul>
</div>
22 changes: 13 additions & 9 deletions application/app/views/projects/show/_project_actions.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@
<a href="<%= files_app_url(project.download_dir) %>"
target="_blank"
class="btn btn-sm btn-outline-secondary"
title="<%= t('.open_project_folder') %>">
<i class="bi bi-folder-fill"></i>
aria-label="<%= t('.button_open_project_folder_label') %>"
title="<%= t('.button_open_project_folder_title') %>">
<i class="bi bi-folder-fill" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_open_project_folder_label') %></span>
</a>
<%= render partial: '/shared/file_row_date', locals: { date: project.creation_date, title: t('.creation_date'), classes: ''} %>
<h5 class="mb-0 me-2 fs-4"><%= project.name %></h5>
<%= render partial: '/shared/file_row_date', locals: { date: project.creation_date, title: t('.field_creation_date_title'), classes: ''} %>
<h2 class="mb-0 me-2 fs-4 h5"><%= project.name %></h2>
</div>

<div class="d-flex align-items-center gap-2 flex-grow-1">
<%= render partial: 'shared/inline_field_submit', locals: {
url: project_upload_bundles_path(project_id: project.id),
type: 'form',
field_name: 'remote_repo_url',
label: t('.create_upload_bundle'),
label: t('.button_create_upload_bundle_label'),
title: t('.button_create_upload_bundle_title'),
class: 'btn-sm btn-outline-secondary',
icon: 'bi bi-folder-plus',
placeholder: t('.create_upload_bundle_placeholder'),
placeholder: t('.button_create_upload_bundle_placeholder'),
} %>

<%= render partial: "shared/button_to", locals: {
url: project_path(id: project.id),
method: 'DELETE',
title: t('.delete_project'),
title: t('.button_delete_project_title'),
class: "btn-sm btn-outline-secondary icon-hover-danger",
icon: "bi bi-trash",
modal_id: 'modal-delete-confirmation',
Expand All @@ -36,8 +39,9 @@
<a href="<%= files_app_url(Project.project_metadata_dir(project.id)) %>"
target="_blank"
class="btn btn-sm btn-outline-secondary"
title="<%= t('.open_project_metadata') %>">
<i class="bi bi-card-list"></i>
title="<%= t('.button_open_project_metadata_title') %>">
<i class="bi bi-card-list" aria-hidden="true"></i>
<span class="visually-hidden"><%= t('.button_open_project_metadata_title') %></span>
</a>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions application/app/views/projects/show/_project_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a class="nav-link text-secondary active" id="<%= tab_label_for(project) %>" data-bs-toggle="tab"
href="<%= tab_href_for(project) %>" role="tab"
aria-controls="<%= tab_anchor_for(project) %>" aria-selected="true">
<i class="bi bi-download me-1"></i><%= t('.download_files') %>
<i class="bi bi-download me-1" aria-hidden="true"></i><%= t('.tab_download_files_text') %>
</a>
</li>

Expand All @@ -14,7 +14,7 @@
<a class="nav-link text-secondary" id="<%= tab_label_for(upload_bundle) %>" data-bs-toggle="tab"
href="<%= tab_href_for(upload_bundle) %>" role="tab"
aria-controls="<%= tab_anchor_for(upload_bundle) %>" aria-selected="false">
<i class="bi bi-upload me-1"></i><%= upload_bundle.name %>
<i class="bi bi-upload me-1" aria-hidden="true"></i><%= upload_bundle.name %>
</a>
</li>
<% end %>
Expand Down
14 changes: 8 additions & 6 deletions application/app/views/projects/show/_upload_actions.html.erb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="card mb-3 shadow-sm rounded overflow-hidden">
<section class="card mb-3 shadow-sm rounded overflow-hidden">
<div class="card-header d-flex justify-content-between align-items-center bg-light-darker">
<div class="d-flex align-items-center gap-3">
<%= render partial: '/shared/file_row_date', locals: { date: bundle.creation_date, title: t('.creation_date') } if bundle.creation_date %>
<%= render partial: '/shared/file_row_date', locals: { date: bundle.creation_date, title: t('.field_creation_date_title') } if bundle.creation_date %>

<div>
<!-- PROJECT METADATA -->
<div class="d-flex flex-column">
<h5 class="mb-0"><%= bundle.name %></h5>
<h3 class="mb-0 h5"><%= bundle.name %></h3>
</div>
</div>

Expand All @@ -24,9 +24,11 @@
data-lazy-loader-url-value="<%= file_browser_path %>"
data-file-drop-activation-file-drop-id-value="<%= file_target_id %>"
data-action="click->lazy-loader#toggle click->file-drop-activation#toggleFileDrop"
title="<%= t('.button_add_files_title') %>"
aria-label="<%= t('.button_add_files_title') %>"
class="btn btn-outline-secondary btn-sm">
<i class="bi bi-plus-square-fill"></i>
<span class="ps-1"><%= t('.add_files') %></span>
<i class="bi bi-plus-square-fill" aria-hidden="true"></i>
<span class="ps-1"><%= t('.button_add_files_text') %></span>
</button>

<%= render partial: "shared/button_to", locals: {
Expand All @@ -45,4 +47,4 @@

<%= render partial: upload_bundle_connector_actions_bar(bundle), locals: { upload_bundle: bundle } %>

</div>
</section>
Loading
Loading