Skip to content

Commit

Permalink
Clipboard component for copying PUID's (#756)
Browse files Browse the repository at this point in the history
* refactor: Renamed clipboard component to token component

* chore: Fixed i18n conflicts

* chore: Updated token tests

* chore: Fixed missed file rename

* chore: Update dashboard groups to have clipboard for puid

* chore: Updated i18n

* test: Added testing for clipboard component

* chore: Added missing test file

* style: Cleaned up colours for better accessibility

* chore: Fixed dark styling

* chore: Fixed colour issues... again

* refactor: Updated to use more of a pill style

* style: Fixed colour accessibility issue

* refactor: Added puid to dashboard projects

* chore: Created PuidComponent to reduce repeated code

* chore: Oops wrong value

* refactor: Made html more semantic and easier to read in UI
  • Loading branch information
joshsadam authored Sep 18, 2024
1 parent bb885ff commit d54efd9
Show file tree
Hide file tree
Showing 28 changed files with 328 additions and 224 deletions.
107 changes: 31 additions & 76 deletions app/components/clipboard_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,80 +1,35 @@
<%= render Viral::BaseComponent.new(tag: 'div', **system_arguments, data: {
controller: "clipboard",
"clipboard-item-value": value }) do %>
<div class="relative mr-2 grow">
<input
type="text"
data-clipboard-target="input"
class="
w-full
font-mono
text-sm
text-slate-900
border
border-slate-300
rounded-md
bg-slate-50
focus:ring-primary-500
focus:border-primary-500
dark:bg-slate-700
dark:border-slate-600
dark:placeholder-slate-400
dark:text-white
dark:focus:ring-primary-500
dark:focus:border-primary-500
"
value="<%= '*' * value.length %>"
readonly
aria-label="<%= label %>"
>
<button
type="button"
class="
absolute
inset-y-0
bottom-0
right-0
p-2
rounded-r-md
focus:outline-none
"
data-action="
click->clipboard#toggleVisibility
"
aria-label="<%= t(:'components.clipboard.toggle_visible') %>"
>
<span data-clipboard-target="hide">
<%= viral_icon(name: "eye", classes: "w-5 h-5 text-slate-500 dark:text-slate-400") %>
</span>
<span data-clipboard-target="view" class="flex items-center hidden">
<%= viral_icon(name: "eye_slash", classes: "w-5 h-5") %>
</span>
</button>
</div>
<span class="flex items-center space-x-1" data-controller="clipboard">
<span>
<% if content.present? %>
<%= content %>
<% else %>
<%= value %>
<% end %>
</span>
<button
type="submit"
data-action="
click->clipboard#copyToClipboard
"
type="button"
title="<%= t("components.clipboard.title") %>"
data-action="clipboard#copy"
aria-label="<%= t("components.clipboard.title") %>"
data-clipboard-target="button"
class="<%= button_classes %>"
value="<%= value %>"
>
<%= viral_icon(
name: :clipboard,
color: :white,
classes: "size-3 pointer-events-none",
) %>
</button>
<div
data-clipboard-target="content"
role="tooltip"
class="
focus:outline-none
text-sm
text-white
bg-primary-700
hover:bg-primary-800
focus:ring-4
focus:ring-primary-300
rounded-md
py-2
px-4
dark:text-white
dark:bg-primary-600
dark:hover:bg-primary-700
dark:focus:ring-primary-800
absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white
transition-opacity bg-green-600 rounded-lg shadow-sm opacity-80 duration-600
tooltip dark:bg-green-700
"
>
<span data-clipboard-target="initial"><%= t("components.clipboard.copy") %></span>
<span data-clipboard-target="copied" class="flex items-center hidden"><%= viral_icon(name: "check", classes: "w-5 h-5") %>
<%= t("components.clipboard.copied") %></span>
</button>
<% end %>
<%= t("components.clipboard.copied") %>
</div>
</span>
19 changes: 8 additions & 11 deletions app/components/clipboard_component.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# frozen_string_literal: true

# Clipboard component for copying text to clipboard
class ClipboardComponent < Component
attr_reader :value, :label

def initialize(value:, aria_label:, **system_arguments)
# Clipboard component for copying to clipboard
class ClipboardComponent < Viral::Component
def initialize(value:, button_classes: '')
@value = value
@label = aria_label
@system_arguments = system_arguments
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
'flex'
)
@button_classes = button_classes
end

private

attr_reader :value, :button_classes
end
49 changes: 25 additions & 24 deletions app/components/namespace_tree/row/row_contents_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,32 @@
<div class="flex flex-col namespace-text">
<div class="namespace-text grow shrink">
<div class="flex flex-wrap items-center mr-3 font-semibold title">
<div class="flex flex-col space-y-1.5">
<div class="space-x-2">
<% if @namespace.type == "Group" %>
<%= link_to @namespace.name, group_path(@namespace), data: { turbo: false } %>
<% else %>
<%= link_to @namespace.name,
project_samples_path(@namespace.project),
data: {
turbo: false,
} %>
<% end %>
<%= viral_pill(
text:
t(
:"members.access_levels.level_#{Member.effective_access_level(@namespace, Current.user)}",
),
color: "transparent",
border: true,
classes: "ml-2",
) %></div>
<span class="text-sm text-gray-500 dark:text-gray-400">
<%= @namespace.puid %>
</span>
<div class="flex items-center space-x-2">
<% if @namespace.type == "Group" %>
<%= link_to @namespace.name, group_path(@namespace), data: { turbo: false } %>
<% else %>
<%= link_to @namespace.name,
project_samples_path(@namespace.project),
data: {
turbo: false,
} %>
<% end %>
<%= render PuidComponent.new(puid: @namespace.puid) %>
<%= viral_pill(
text:
t(
:"members.access_levels.level_#{Member.effective_access_level(@namespace, Current.user)}",
),
color: "transparent",
border: true,
classes: "ml-2",
) %>
</div>

</div>
<p class="text-sm text-gray-500 dark:text-gray-400">
<%= @namespace.description %>
</p>
</div>
</div>
</div>
Expand All @@ -66,7 +67,7 @@
<% end %>
<%= viral_tooltip(title: t(:'.stats.subnamespaces')) do %>
<span class="flex items-center">
<%= viral_icon(name: :squares_2x2, color: :subdued, classes: "h-5 w-5") %><%= @namespace.children.count %>
<%= viral_icon(name: :squares_2x2, color: :subdued, classes: "h-2 w-5") %><%= @namespace.children.count %>
</span>
<% end %>
<% end %>
Expand Down
12 changes: 12 additions & 0 deletions app/components/puid_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<% puid_classes =
"inline-flex items-center pl-2 text-xs font-medium rounded-full #{
"text-indigo-800 bg-indigo-100 border-indigo-300 dark:bg-indigo-900 dark:text-indigo-300 dark:border-indigo-700"
}" %>

<code class="<%= puid_classes %>" aria-label="PUID: <%= puid %>">
<%= render ClipboardComponent.new(
value: puid,
button_classes:
"p-1 bg-white border border-indigo-300 rounded-full hover:bg-indigo-100 text-indigo-800 hover:text-indigo-900 dark:bg-indigo-900 dark:text-indigo-300 dark:border-indigo-700 dark:hover:bg-indigo-800",
) %>
</code>
10 changes: 10 additions & 0 deletions app/components/puid_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

# Represents a component for displaying a PUID (Persistent Unique Identifier)
class PuidComponent < ViewComponent::Base
attr_reader :puid

def initialize(puid:)
@puid = puid
end
end
47 changes: 47 additions & 0 deletions app/components/token_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<%= render Viral::BaseComponent.new(tag: 'div', **system_arguments, data: {
controller: "token",
"token-item-value": value }) do %>
<div class="relative mr-2 grow">
<input
type="text"
data-token-target="input"
class="
w-full font-mono text-sm border rounded-md text-slate-900 border-slate-300
bg-slate-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-slate-700
dark:border-slate-600 dark:placeholder-slate-400 dark:text-white
dark:focus:ring-primary-500 dark:focus:border-primary-500
"
value="<%= '*' * value.length %>"
readonly
aria-label="<%= label %>"
>
<button
type="button"
class="
absolute inset-y-0 bottom-0 right-0 p-2 rounded-r-md focus:outline-none
"
data-action="click->token#toggleVisibility"
aria-label="<%= t(:'components.token.toggle_visible') %>"
>
<span data-token-target="hide">
<%= viral_icon(name: "eye", classes: "w-5 h-5 text-slate-500 dark:text-slate-400") %>
</span>
<span data-token-target="view" class="flex items-center hidden">
<%= viral_icon(name: "eye_slash", classes: "w-5 h-5") %>
</span>
</button>
</div>
<button
type="submit"
data-action="click->token#copyToClipboard"
class="
px-4 py-2 text-sm text-white rounded-md focus:outline-none bg-primary-700
hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:text-white
dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800
"
>
<span data-token-target="initial"><%= t("components.token.copy") %></span>
<span data-token-target="copied" class="flex items-center hidden"><%= viral_icon(name: "check", classes: "w-5 h-5") %>
<%= t("components.token.copied") %></span>
</button>
<% end %>
16 changes: 16 additions & 0 deletions app/components/token_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# Token component for displaying a token
class TokenComponent < Component
attr_reader :value, :label

def initialize(value:, aria_label:, **system_arguments)
@value = value
@label = aria_label
@system_arguments = system_arguments
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
'flex'
)
end
end
20 changes: 5 additions & 15 deletions app/components/viral/page_header_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
<div class=" justify-between w-full mb-4 page-header sm:flex ">
<div class="justify-between w-full mb-4 page-header sm:flex">
<div class="flex items-start">
<% if icon.present? %>
<span class="mr-2"><%= icon %></span>
<% end %>

<div>
<% if @id %>
<div class="flex items-center">
<div class="flex items-center space-x-2">
<h1
class="
text-2xl
font-medium
leading-tight
text-slate-900
dark:text-white
inline
inline text-2xl font-medium leading-tight text-slate-900 dark:text-white
"
><%= title %></h1>
<%= viral_pill(text: id, color: id_color, classes: "ml-2") %>
<%= render PuidComponent.new(puid: id) %>
</div>
<% else %>
<h1
class="
text-2xl
font-medium
leading-tight
text-slate-900
dark:text-white
inline
inline text-2xl font-medium leading-tight text-slate-900 dark:text-white
"
><%= title %></h1>
<% end %>
Expand Down
42 changes: 15 additions & 27 deletions app/javascript/controllers/clipboard_controller.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
import { Controller } from "@hotwired/stimulus";
import { Tooltip } from "flowbite";

export default class extends Controller {
static targets = ["contents", "initial", "input", "copied", "hide", "view"];
static values = { item: String };
static targets = ["button", "content"];
#tooltip;

connect() {
this.visible = false;
this.element.setAttribute("data-controller-connected", "true");
this.#tooltip = new Tooltip(this.contentTarget, this.buttonTarget, {
placement: "top",
triggerType: "none",
});
}

copyToClipboard() {
navigator.clipboard.writeText(this.itemValue);

this.initialTarget.classList.add("hidden");
this.copiedTarget.classList.remove("hidden");
setTimeout(() => {
this.initialTarget.classList.remove("hidden");
this.copiedTarget.classList.add("hidden");
}, 2000);
copy(e) {
e.stopImmediatePropagation();
navigator.clipboard.writeText(e.target.value).then(this.#notify.bind(this));
}

toggleVisibility() {
if (this.visible) {
this.hideTarget.classList.remove("hidden");
this.viewTarget.classList.add("hidden");
this.inputTarget.value = Array.prototype.join.call(
{ length: this.itemValue.length },
"*"
);
} else {
this.hideTarget.classList.add("hidden");
this.viewTarget.classList.remove("hidden");
this.inputTarget.value = this.itemValue;
}
this.visible = !this.visible;
#notify() {
this.#tooltip.show();
setTimeout(() => {
this.#tooltip.hide();
}, 1000);
}
}
Loading

0 comments on commit d54efd9

Please sign in to comment.