Skip to content
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

wip: Enable viewing all workflow form sections at once #2310

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
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
40 changes: 15 additions & 25 deletions frontend/src/components/ui/tab-group/tab-group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import clsx from "clsx";
import { html, type PropertyValues } from "lit";
import {
customElement,
Expand All @@ -10,7 +9,7 @@ import type { TabClickDetail, TabGroupTab } from "./tab";
import { type TabGroupPanel } from "./tab-panel";

import { TailwindElement } from "@/classes/TailwindElement";
import { tw } from "@/utils/tailwind";
import { pageSectionsWithNav } from "@/layouts/pageSectionsWithNav";

/**
* @example Usage:
Expand All @@ -33,6 +32,10 @@ export class TabGroup extends TailwindElement {
@property({ type: String })
placement: "top" | "start" = "top";

/* Nav sticky */
@property({ type: Boolean })
sticky = true;

@property({ type: String, noAccessor: true, reflect: true })
role = "tablist";

Expand Down Expand Up @@ -61,29 +64,16 @@ export class TabGroup extends TailwindElement {
}

render() {
return html`
<div
class=${clsx(
tw`flex flex-col`,
this.placement === "start" && tw`gap-8 lg:flex-row`,
)}
>
<div
class=${clsx(
tw`flex flex-1 flex-col gap-2`,
this.placement === "start"
? tw`lg:sticky lg:top-2 lg:max-w-[16.5rem] lg:self-start`
: tw`lg:flex-row`,
)}
@keydown=${this.onKeyDown}
>
<slot name="nav" @btrix-select-tab=${this.onSelectTab}></slot>
</div>
<div class="flex-1">
<slot></slot>
</div>
</div>
`;
return pageSectionsWithNav({
nav: html`<slot
name="nav"
@btrix-select-tab=${this.onSelectTab}
@keydown=${this.onKeyDown}
></slot>`,
main: html`<slot></slot>`,
placement: this.placement,
sticky: this.sticky,
});
}

private handleActiveChange() {
Expand Down
136 changes: 26 additions & 110 deletions frontend/src/components/ui/tab-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,10 @@ const DEFAULT_PANEL_ID = "default-panel";
// postcss-lit-disable-next-line
export const TWO_COL_SCREEN_MIN_CSS = css`64.5rem`;

/**
* @deprecated Use `btrix-tab-group`
*
* Tab list
*
* Usage example:
* ```ts
* <btrix-tab-list activePanel="one">
* <btrix-tab slot="nav" name="one">One</btrix-tab>
* <btrix-tab slot="nav" name="two">Two</btrix-tab>
* </btrix-tab-list>
*
* <btrix-tab-panel name="one">Tab one content</btrix-tab-panel>
* <btrix-tab-panel name="two">Tab two content</btrix-tab-panel>
* ```
*/

@customElement("btrix-tab-panel")
export class TabPanel extends TailwindElement {
@property({ type: String })
name?: string;
const tabTagName = "btrix-tab-list-tab" as const;

@property({ type: Boolean })
active = false;

render() {
return html`
<div
class="flex-auto aria-hidden:hidden"
role="tabpanel"
id=${ifDefined(this.name)}
aria-hidden=${!this.active}
>
<slot></slot>
</div>
`;
}
}

/**
* @deprecated Use `btrix-tab-group`
*/
@customElement("btrix-tab")
export class Tab extends TailwindElement {
@customElement(tabTagName)
export class TabListTab extends TailwindElement {
// ID of panel the tab labels/controls
@property({ type: String })
name?: string;
Expand All @@ -65,7 +25,7 @@ export class Tab extends TailwindElement {
render() {
return html`
<li
class="cursor-pointer px-3 py-4 font-semibold leading-tight text-neutral-500 transition-colors duration-fast aria-disabled:cursor-default aria-selected:text-primary-600"
class="cursor-pointer p-3 font-semibold leading-tight text-neutral-500 transition-colors duration-fast aria-disabled:cursor-default aria-selected:text-primary-600"
role="tab"
aria-selected=${this.active}
aria-controls=${ifDefined(this.name)}
Expand All @@ -78,11 +38,18 @@ export class Tab extends TailwindElement {
}
}

type TabElement = Tab & HTMLElement;
type TabPanelElement = TabPanel & HTMLElement;
type TabElement = TabListTab & HTMLElement;

/**
* @deprecated Use `btrix-tab-group`
* Tab list with indicator
*
* Usage example:
* ```ts
* <btrix-tab-list tab="one">
* <btrix-tab name="one">One</btrix-tab>
* <btrix-tab name="two">Two</btrix-tab>
* </btrix-tab-list>
* ```
*/
@customElement("btrix-tab-list")
export class TabList extends TailwindElement {
Expand All @@ -91,30 +58,6 @@ export class TabList extends TailwindElement {
--track-width: 4px;
}

.btrix-tab-list-container {
display: grid;
grid-template-areas:
"menu"
"header"
"main";
grid-template-columns: 1fr;
grid-column-gap: 1.5rem;
grid-row-gap: 1rem;
}

@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
.btrix-tab-list-container {
grid-template-areas:
". header"
"menu main";
grid-template-columns: 16.5rem 1fr;
}
}

.navWrapper {
grid-area: menu;
}

@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
.navWrapper {
overflow: initial;
Expand Down Expand Up @@ -173,7 +116,7 @@ export class TabList extends TailwindElement {
position: absolute;
width: var(--track-width);
border-radius: var(--track-width);
background-color: var(--sl-color-primary-600);
background-color: var(--sl-color-primary-500);
}

@media only screen and (min-width: ${TWO_COL_SCREEN_MIN_CSS}) {
Expand All @@ -184,9 +127,9 @@ export class TabList extends TailwindElement {
}
`;

// ID of visible panel
// ID of active tab
@property({ type: String })
activePanel: string = DEFAULT_PANEL_ID;
tab: string = DEFAULT_PANEL_ID;

// If panels are linear, the current panel in progress
@property({ type: String })
Expand All @@ -202,8 +145,8 @@ export class TabList extends TailwindElement {
private readonly indicatorElem!: HTMLElement;

updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has("activePanel") && this.activePanel) {
this.onActiveChange(!changedProperties.get("activePanel"));
if (changedProperties.has("tab") && this.tab) {
this.onActiveChange(!changedProperties.get("tab"));
}
if (changedProperties.has("progressPanel") && this.progressPanel) {
this.onProgressChange(!changedProperties.get("progressPanel"));
Expand Down Expand Up @@ -235,15 +178,7 @@ export class TabList extends TailwindElement {
}

render() {
return html`
<div class="btrix-tab-list-container">
<div class="navWrapper min-w-0">${this.renderNav()}</div>
<div class="header"><slot name="header"></slot></div>
<div class="content">
<slot></slot>
</div>
</div>
`;
return html`<div class="navWrapper min-w-0">${this.renderNav()}</div>`;
}

renderNav() {
Expand All @@ -256,35 +191,26 @@ export class TabList extends TailwindElement {
class="nav ${this.progressPanel ? "linear" : "nonlinear"} ${this
.hideIndicator
? "hide-indicator"
: "show-indicator"} -m-3 overflow-x-hidden p-3"
: "show-indicator"} -mx-3 overflow-x-hidden px-3"
>
<div class="track" role="presentation">
<div class="indicator" role="presentation"></div>
</div>

<ul class="tablist -m-3 overflow-x-auto p-3" role="tablist">
<slot name="nav"></slot>
<ul class="tablist -mx-3 overflow-x-auto px-3" role="tablist">
<slot></slot>
</ul>
</div>
</sl-resize-observer>
`;
}

private getPanels(): TabPanelElement[] {
const slotElems = this.renderRoot
.querySelector<HTMLSlotElement>(".content slot:not([name])")!
.assignedElements();
return ([...slotElems] as TabPanelElement[]).filter(
(el) => el.tagName.toLowerCase() === "btrix-tab-panel",
);
}

private getTabs(): TabElement[] {
const slotElems = this.renderRoot
.querySelector<HTMLSlotElement>("slot[name='nav']")!
.querySelector<HTMLSlotElement>("slot")!
.assignedElements();
return ([...slotElems] as TabElement[]).filter(
(el) => el.tagName.toLowerCase() === "btrix-tab",
(el) => el.tagName.toLowerCase() === tabTagName,
);
}

Expand All @@ -305,7 +231,7 @@ export class TabList extends TailwindElement {

private onActiveChange(isFirstChange: boolean) {
this.getTabs().forEach((tab) => {
if (tab.name === this.activePanel) {
if (tab.name === this.tab) {
tab.active = true;

if (!this.progressPanel) {
Expand All @@ -315,15 +241,5 @@ export class TabList extends TailwindElement {
tab.active = false;
}
});
this.getPanels().forEach((panel) => {
panel.active = panel.name === this.activePanel;
if (panel.active) {
panel.style.display = "flex";
panel.setAttribute("aria-hidden", "false");
} else {
panel.style.display = "none";
panel.setAttribute("aria-hidden", "true");
}
});
}
}
39 changes: 9 additions & 30 deletions frontend/src/components/utils/observable.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,32 @@
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators.js";
import { customElement, property } from "lit/decorators.js";

type IntersectionEventDetail = {
entry: IntersectionObserverEntry;
};
export type IntersectEvent = CustomEvent<IntersectionEventDetail>;
import { ObservableController } from "@/controllers/observable";

/**
* Observe element with Intersection Observer API.
*
* @example Usage:
* ```
* <btrix-observable @intersect=${console.log}>
* <btrix-observable @btrix-intersect=${console.log}>
* Observe me!
* </btrix-observable>
* ```
*
* @event intersect { entry: IntersectionObserverEntry }
* @fires btrix-intersect IntersectionEventDetail
*/
@customElement("btrix-observable")
export class Observable extends LitElement {
@query(".target")
private readonly target?: HTMLElement;
@property({ type: Object })
options?: IntersectionObserverInit;

private observer?: IntersectionObserver;

connectedCallback(): void {
super.connectedCallback();
this.observer = new IntersectionObserver(this.handleIntersect);
}

disconnectedCallback(): void {
this.observer?.disconnect();
super.disconnectedCallback();
}
private readonly observable = new ObservableController(this);

firstUpdated() {
this.observer?.observe(this.target!);
this.observable.observe(this);
}

private readonly handleIntersect = ([entry]: IntersectionObserverEntry[]) => {
this.dispatchEvent(
new CustomEvent<IntersectionEventDetail>("intersect", {
detail: { entry },
}),
);
};

render() {
return html`<div class="target"><slot></slot></div>`;
return html`<slot></slot>`;
}
}
Loading
Loading