mirror of
https://github.com/ae-utbm/sith.git
synced 2025-06-18 17:15:22 +00:00
Replace tab macro with new tab web component
This commit is contained in:
parent
2dd4fd5c71
commit
c904e41ea3
@ -7,7 +7,6 @@ export class Tab extends HTMLElement {
|
|||||||
static observedAttributes = ["title", "active"];
|
static observedAttributes = ["title", "active"];
|
||||||
private description = "";
|
private description = "";
|
||||||
private inner = "";
|
private inner = "";
|
||||||
private initialized = false;
|
|
||||||
private active = false;
|
private active = false;
|
||||||
|
|
||||||
attributeChangedCallback(name: string, _oldValue?: string, newValue?: string) {
|
attributeChangedCallback(name: string, _oldValue?: string, newValue?: string) {
|
||||||
@ -22,33 +21,30 @@ export class Tab extends HTMLElement {
|
|||||||
if (name === "title") {
|
if (name === "title") {
|
||||||
this.description = newValue;
|
this.description = newValue;
|
||||||
}
|
}
|
||||||
|
this.dispatchEvent(new CustomEvent("ui-tab-updated", { bubbles: true }));
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
getButtonTemplate() {
|
||||||
if (!this.initialized) {
|
return html`
|
||||||
return;
|
|
||||||
}
|
|
||||||
const active = this.active ? "active" : "";
|
|
||||||
const tabContent = this.getContentHtml();
|
|
||||||
const content = html`
|
|
||||||
<button
|
<button
|
||||||
role="tab"
|
role="tab"
|
||||||
?aria-selected=${active}
|
?aria-selected=${this.active}
|
||||||
class="tab-header clickable ${active}"
|
class="tab-header clickable ${this.active ? "active" : ""}"
|
||||||
@click="${() => this.setActive(true)}"
|
@click="${() => this.setActive(true)}"
|
||||||
>
|
>
|
||||||
${this.description}
|
${this.description}
|
||||||
</button>
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
getContentTemplate() {
|
||||||
|
return html`
|
||||||
<section
|
<section
|
||||||
class="tab-content"
|
class="tab-section"
|
||||||
?hidden=${!active}
|
?hidden=${!this.active}
|
||||||
>
|
>
|
||||||
${unsafeHTML(tabContent)}
|
${unsafeHTML(this.getContentHtml())}
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
render(content, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(value: boolean) {
|
setActive(value: boolean) {
|
||||||
@ -62,12 +58,10 @@ export class Tab extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.inner = this.innerHTML;
|
this.inner = this.innerHTML;
|
||||||
this.innerHTML = "";
|
this.innerHTML = "";
|
||||||
this.initialized = true;
|
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getContentHtml() {
|
getContentHtml() {
|
||||||
const content = this.getElementsByClassName("tab-content")[0];
|
const content = this.getElementsByClassName("tab-section")[0];
|
||||||
if (content !== undefined) {
|
if (content !== undefined) {
|
||||||
return content.innerHTML;
|
return content.innerHTML;
|
||||||
}
|
}
|
||||||
@ -75,19 +69,23 @@ export class Tab extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContentHtml(value: string) {
|
setContentHtml(value: string) {
|
||||||
const content = this.getElementsByClassName("tab-content")[0];
|
const content = this.getElementsByClassName("tab-section")[0];
|
||||||
if (content !== undefined) {
|
if (content !== undefined) {
|
||||||
content.innerHTML = value;
|
content.innerHTML = value;
|
||||||
}
|
}
|
||||||
this.inner = value;
|
this.inner = value;
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@registerComponent("ui-tab-group")
|
@registerComponent("ui-tab-group")
|
||||||
export class TabGroup extends HTMLElement {
|
export class TabGroup extends HTMLElement {
|
||||||
|
private node: HTMLDivElement;
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.classList.add("tabs", "shadow");
|
this.node = document.createElement("div");
|
||||||
|
this.node.classList.add("tabs", "shadow");
|
||||||
|
this.appendChild(this.node);
|
||||||
|
|
||||||
this.addEventListener("ui-tab-activated", (event: CustomEvent) => {
|
this.addEventListener("ui-tab-activated", (event: CustomEvent) => {
|
||||||
const target = event.detail as Tab;
|
const target = event.detail as Tab;
|
||||||
for (const tab of this.getElementsByTagName("ui-tab") as HTMLCollectionOf<Tab>) {
|
for (const tab of this.getElementsByTagName("ui-tab") as HTMLCollectionOf<Tab>) {
|
||||||
@ -96,5 +94,27 @@ export class TabGroup extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.addEventListener("ui-tab-updated", () => {
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const tabs = Array.prototype.slice.call(
|
||||||
|
this.getElementsByTagName("ui-tab"),
|
||||||
|
) as Tab[];
|
||||||
|
render(
|
||||||
|
html`
|
||||||
|
<div class="tab-headers">
|
||||||
|
${tabs.map((tab) => tab.getButtonTemplate())}
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
${tabs.map((tab) => tab.getContentTemplate())}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
this.node,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
53
core/static/core/components/tabs.scss
Normal file
53
core/static/core/components/tabs.scss
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@import "core/static/core/colors";
|
||||||
|
|
||||||
|
ui-tab-group {
|
||||||
|
*[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.tab-headers {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
background-color: $primary-neutral-light-color;
|
||||||
|
padding: 3px 12px 12px;
|
||||||
|
column-gap: 20px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
|
||||||
|
.tab-header {
|
||||||
|
border: none;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
font-size: 120%;
|
||||||
|
background-color: unset;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 4px solid darken($primary-neutral-light-color, 10%);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:after {
|
||||||
|
border-bottom-color: darken($primary-neutral-light-color, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active:after {
|
||||||
|
border-bottom-color: $primary-dark-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -352,52 +352,6 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
|
||||||
border-radius: 5px;
|
|
||||||
|
|
||||||
.tab-headers {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
background-color: $primary-neutral-light-color;
|
|
||||||
padding: 3px 12px 12px;
|
|
||||||
column-gap: 20px;
|
|
||||||
border-top-left-radius: 5px;
|
|
||||||
border-top-right-radius: 5px;
|
|
||||||
|
|
||||||
.tab-header {
|
|
||||||
border: none;
|
|
||||||
padding-right: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
font-size: 120%;
|
|
||||||
background-color: unset;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 4px solid darken($primary-neutral-light-color, 10%);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:after {
|
|
||||||
border-bottom-color: darken($primary-neutral-light-color, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active:after {
|
|
||||||
border-bottom-color: $primary-dark-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool_bar {
|
.tool_bar {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
@ -245,65 +245,3 @@
|
|||||||
<button type="button" onclick="checkbox_{{form_id}}(true);">{% trans %}Select All{% endtrans %}</button>
|
<button type="button" onclick="checkbox_{{form_id}}(true);">{% trans %}Select All{% endtrans %}</button>
|
||||||
<button type="button" onclick="checkbox_{{form_id}}(false);">{% trans %}Unselect All{% endtrans %}</button>
|
<button type="button" onclick="checkbox_{{form_id}}(false);">{% trans %}Unselect All{% endtrans %}</button>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro tabs(tab_list, attrs = "") %}
|
|
||||||
{# Tab component
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
tab_list: list[tuple[str, str]] The list of tabs to display.
|
|
||||||
Each element of the list is a tuple which first element
|
|
||||||
is the title of the tab and the second element its content
|
|
||||||
attrs: str Additional attributes to put on the enclosing div
|
|
||||||
|
|
||||||
Example:
|
|
||||||
A basic usage would be as follow :
|
|
||||||
|
|
||||||
{{ tabs([("title 1", "content 1"), ("title 2", "content 2")]) }}
|
|
||||||
|
|
||||||
If you want to display more complex logic, you can define macros
|
|
||||||
and use those macros in parameters :
|
|
||||||
|
|
||||||
{{ tabs([("title", my_macro())]) }}
|
|
||||||
|
|
||||||
It's also possible to get and set the currently selected tab using Alpine.
|
|
||||||
Here, the title of the currently selected tab will be displayed.
|
|
||||||
Moreover, on page load, the tab will be opened on "tab 2".
|
|
||||||
|
|
||||||
<div x-data="{current_tab: 'tab 2'}">
|
|
||||||
<p x-text="current_tab"></p>
|
|
||||||
{{ tabs([("tab 1", "Hello"), ("tab 2", "World")], "x-model=current_tab") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
If you want to have translated tab titles, you can enclose the macro call
|
|
||||||
in a with block :
|
|
||||||
|
|
||||||
{% with title=_("title"), content=_("Content") %}
|
|
||||||
{{ tabs([(tab1, content)]) }}
|
|
||||||
{% endwith %}
|
|
||||||
#}
|
|
||||||
<div
|
|
||||||
class="tabs shadow"
|
|
||||||
x-data="{selected: '{{ tab_list[0][0] }}'}"
|
|
||||||
x-modelable="selected"
|
|
||||||
{{ attrs }}
|
|
||||||
>
|
|
||||||
<div class="tab-headers">
|
|
||||||
{% for title, _ in tab_list %}
|
|
||||||
<button
|
|
||||||
class="tab-header clickable"
|
|
||||||
:class="{active: selected === '{{ title }}'}"
|
|
||||||
@click="selected = '{{ title }}'"
|
|
||||||
>
|
|
||||||
{{ title }}
|
|
||||||
</button>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-content">
|
|
||||||
{% for title, content in tab_list %}
|
|
||||||
<section x-show="selected === '{{ title }}'">
|
|
||||||
{{ content }}
|
|
||||||
</section>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
So we give them here.
|
So we give them here.
|
||||||
If the aforementioned bug is resolved, you can remove this. #}
|
If the aforementioned bug is resolved, you can remove this. #}
|
||||||
{% block additional_js %}
|
{% block additional_js %}
|
||||||
|
<script type="module" src="{{ static('bundled/core/components/tabs-index.ts') }}"></script>
|
||||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
@ -19,6 +20,7 @@
|
|||||||
></script>
|
></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block additional_css %}
|
{% block additional_css %}
|
||||||
|
<link rel="stylesheet" href="{{ static("core/components/tabs.scss") }}">
|
||||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||||
<link rel="stylesheet" href="{{ static("subscription/css/subscription.scss") }}">
|
<link rel="stylesheet" href="{{ static("subscription/css/subscription.scss") }}">
|
||||||
@ -34,12 +36,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h3>{% trans %}New subscription{% endtrans %}</h3>
|
<h3>{% trans %}New subscription{% endtrans %}</h3>
|
||||||
<div id="subscription-form">
|
<ui-tab-group id="subscription-form">
|
||||||
{% with title1=_("Existing member"), title2=_("New member") %}
|
<ui-tab title="{% trans %}Existing member{% endtrans %}" active>
|
||||||
{{ tabs([
|
{{ form_fragment(existing_user_form, existing_user_post_url) }}
|
||||||
(title1, form_fragment(existing_user_form, existing_user_post_url)),
|
</ui-tab>
|
||||||
(title2, form_fragment(new_user_form, new_user_post_url)),
|
<ui-tab title="{% trans %}New member{% endtrans %}">
|
||||||
]) }}
|
{{ form_fragment(new_user_form, new_user_post_url) }}
|
||||||
{% endwith %}
|
</ui-tab>
|
||||||
</div>
|
</ui-tab-group>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user