refactor: Update component used in tabbed views (#2300)

- Refactors instances of `btrix-tab-list` except in workflow editor in
preparation for https://github.com/webrecorder/browsertrix/issues/2169
- Removes the visual space above navigation item since many tab headings
describe the first section in the tab, rather than the entire tab itself
This commit is contained in:
sua yoo 2025-01-14 10:23:19 -08:00 committed by GitHub
parent a028ed1808
commit c563b622fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 147 additions and 125 deletions

View File

@ -1,3 +1,4 @@
import clsx from "clsx";
import { html, type PropertyValues } from "lit";
import {
customElement,
@ -9,9 +10,18 @@ import type { TabClickDetail, TabGroupTab } from "./tab";
import { type TabGroupPanel } from "./tab-panel";
import { TailwindElement } from "@/classes/TailwindElement";
import { tw } from "@/utils/tailwind";
/**
* TODO consolidate with btrix-tab-list
* @example Usage:
* ```ts
* <btrix-tab-group>
* <btrix-tab-group-tab slot="nav" panel="first">First</btrix-tab-group-tab>
* <btrix-tab-group-tab slot="nav" panel="second">Second</btrix-tab-group-tab>
* <btrix-tab-group-panel name="first">First tab content</btrix-tab-group-panel>
* <btrix-tab-group-panel name="second">First tab content</btrix-tab-group-panel>
* </btrix-tab-group>
* ```
*/
@customElement("btrix-tab-group")
export class TabGroup extends TailwindElement {
@ -19,6 +29,10 @@ export class TabGroup extends TailwindElement {
@property({ type: String, reflect: false })
active = "";
/* Nav placement */
@property({ type: String })
placement: "top" | "start" = "top";
@property({ type: String, noAccessor: true, reflect: true })
role = "tablist";
@ -48,10 +62,27 @@ export class TabGroup extends TailwindElement {
render() {
return html`
<div class="mb-4 flex gap-2" @keydown=${this.onKeyDown}>
<slot name="nav" @btrix-select-tab=${this.onSelectTab}></slot>
<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>
<slot></slot>
`;
}

View File

@ -3,9 +3,6 @@ import { customElement, property } from "lit/decorators.js";
import { TailwindElement } from "@/classes/TailwindElement";
/**
* TODO consolidate with btrix-tab-list btrix-tab-panel
*/
@customElement("btrix-tab-group-panel")
export class TabGroupPanel extends TailwindElement {
@property({ type: String })

View File

@ -6,8 +6,6 @@ import { NavigationButton } from "@/components/ui/navigation/navigation-button";
export type TabClickDetail = { panel: string };
/**
* TODO consolidate with btrix-tab-list btrix-tab
*
* @fires btrix-select-tab
*/
@customElement("btrix-tab-group-tab")

View File

@ -9,6 +9,8 @@ const DEFAULT_PANEL_ID = "default-panel";
export const TWO_COL_SCREEN_MIN_CSS = css`64.5rem`;
/**
* @deprecated Use `btrix-tab-group`
*
* Tab list
*
* Usage example:
@ -45,6 +47,9 @@ export class TabPanel extends TailwindElement {
}
}
/**
* @deprecated Use `btrix-tab-group`
*/
@customElement("btrix-tab")
export class Tab extends TailwindElement {
// ID of panel the tab labels/controls
@ -76,6 +81,9 @@ export class Tab extends TailwindElement {
type TabElement = Tab & HTMLElement;
type TabPanelElement = TabPanel & HTMLElement;
/**
* @deprecated Use `btrix-tab-group`
*/
@customElement("btrix-tab-list")
export class TabList extends TailwindElement {
static styles = css`

View File

@ -149,25 +149,15 @@ export class AccountSettings extends BtrixElement {
classNames: tw`mb-3 lg:mb-5`,
})}
<btrix-tab-list activePanel=${this.activeTab} hideIndicator>
<header slot="header" class="flex h-7 items-end justify-between">
${choose(
this.activeTab,
[
[Tab.Profile, () => html`<h2>${msg("Display Name")}</h2>`],
[Tab.Security, () => html`<h2>${msg("Password")}</h2>`],
],
() => html`<h2>${this.tabLabels[this.activeTab]}</h2>`,
)}
</header>
<btrix-tab-group active=${this.activeTab} placement="start">
${this.renderTab(Tab.Profile)} ${this.renderTab(Tab.Security)}
<btrix-tab-panel name=${Tab.Profile}>
<btrix-tab-group-panel name=${Tab.Profile}>
${this.renderProfile()}
</btrix-tab-panel>
<btrix-tab-panel name=${Tab.Security}>
</btrix-tab-group-panel>
<btrix-tab-group-panel name=${Tab.Security}>
${this.renderSecurity()}
</btrix-tab-panel>
</btrix-tab-list>
</btrix-tab-group-panel>
</btrix-tab-group>
`;
}
@ -175,6 +165,7 @@ export class AccountSettings extends BtrixElement {
if (!this.userInfo) return;
return html`
<h2 class="mb-2 text-lg font-medium">${msg("Display Name")}</h2>
<form class="mb-5 rounded-lg border" @submit=${this.onSubmitName}>
<div class="p-4">
<p class="mb-2">
@ -257,6 +248,7 @@ export class AccountSettings extends BtrixElement {
private renderSecurity() {
return html`
<h2 class="mb-2 text-lg font-medium">${msg("Password")}</h2>
<form class="rounded-lg border" @submit=${this.onSubmitPassword}>
<div class="p-4">
<sl-input
@ -304,14 +296,11 @@ export class AccountSettings extends BtrixElement {
}
private renderTab(name: Tab) {
const isActive = name === this.activeTab;
return html`
<btrix-navigation-button
<btrix-tab-group-tab
slot="nav"
panel=${name}
href=${`/account/settings/${name}`}
.active=${isActive}
aria-selected=${isActive}
@click=${this.navigate.link}
>
${choose(name, [
@ -325,7 +314,7 @@ export class AccountSettings extends BtrixElement {
],
])}
${this.tabLabels[name]}
</btrix-navigation-button>
</btrix-tab-group-tab>
`;
}

View File

@ -543,7 +543,7 @@ export class ArchivedItemDetail extends BtrixElement {
};
return html`
<nav
class="sticky top-0 -mx-3 flex flex-row gap-2 overflow-x-auto px-3 pb-4 text-center md:mt-10 md:flex-col md:text-start"
class="sticky top-0 -mx-3 flex flex-row gap-2 overflow-x-auto px-3 pb-4 text-center md:flex-col md:text-start"
role="menu"
>
${renderNavItem({
@ -716,7 +716,7 @@ export class ArchivedItemDetail extends BtrixElement {
private renderTitle(title: string | TemplateResult<1>) {
return html`<h2
class="flex items-center gap-2 text-lg font-semibold leading-8"
class="flex items-center gap-2 text-lg font-medium leading-8"
>
${title}
</h2>`;

View File

@ -1,7 +1,13 @@
import { localized, msg, str } from "@lit/localize";
import type { SlInput } from "@shoelace-style/shoelace";
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
import { html, nothing, unsafeCSS, type PropertyValues } from "lit";
import {
html,
nothing,
unsafeCSS,
type PropertyValues,
type TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { choose } from "lit/directives/choose.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -154,53 +160,7 @@ export class OrgSettings extends BtrixElement {
classNames: tw`mb-3 lg:mb-5`,
})}
<btrix-tab-list activePanel=${this.activePanel} hideIndicator>
<header slot="header" class="flex h-7 items-end justify-between">
${choose(
this.activePanel,
[
[
"members",
() => html`
<h3>${msg("Active Members")}</h3>
<sl-button
href=${`${this.navigate.orgBasePath}/settings/members?invite`}
variant="primary"
size="small"
@click=${this.navigate.link}
>
<sl-icon
slot="prefix"
name="person-add"
aria-hidden="true"
library="default"
></sl-icon>
${msg("Invite New Member")}
</sl-button>
`,
],
["billing", () => html`<h3>${msg("Current Plan")}</h3> `],
[
"crawling-defaults",
() =>
html`<h3 class="flex items-center gap-2">
${msg("Crawling Defaults")}
<sl-tooltip
content=${msg(
"Default settings for all new crawl workflows. Existing workflows will not be affected.",
)}
>
<sl-icon
class="text-base text-neutral-500"
name="info-circle"
></sl-icon>
</sl-tooltip>
</h3>`,
],
],
() => html`<h3>${this.tabLabels[this.activePanel]}</h3>`,
)}
</header>
<btrix-tab-group active=${this.activePanel} placement="start">
${this.renderTab("information", "settings")}
${this.renderTab("members", "settings/members")}
${when(this.appState.settings?.billingEnabled, () =>
@ -208,34 +168,83 @@ export class OrgSettings extends BtrixElement {
)}
${this.renderTab("crawling-defaults", "settings/crawling-defaults")}
<btrix-tab-panel name="information">
<btrix-tab-group-panel name="information">
${this.renderPanelHeader({ title: msg("General") })}
${this.renderInformation()}
<btrix-org-settings-profile></btrix-org-settings-profile>
${this.renderApi()}
</btrix-tab-panel>
<btrix-tab-panel name="members">
</btrix-tab-group-panel>
<btrix-tab-group-panel name="members">
${this.renderPanelHeader({
title: msg("Active Members"),
actions: html`
<sl-button
href=${`${this.navigate.orgBasePath}/settings/members?invite`}
variant="primary"
size="small"
@click=${this.navigate.link}
>
<sl-icon
slot="prefix"
name="person-add"
aria-hidden="true"
library="default"
></sl-icon>
${msg("Invite New Member")}
</sl-button>
`,
})}
${this.renderMembers()}
</btrix-tab-panel>
<btrix-tab-panel name="billing">
</btrix-tab-group-panel>
<btrix-tab-group-panel name="billing">
${this.renderPanelHeader({ title: msg("Current Plan") })}
<btrix-org-settings-billing
.salesEmail=${this.appState.settings?.salesEmail}
></btrix-org-settings-billing>
</btrix-tab-panel>
<btrix-tab-panel name="crawling-defaults">
</btrix-tab-group-panel>
<btrix-tab-group-panel name="crawling-defaults">
${this.renderPanelHeader({
title: msg("Crawling Defaults"),
actions: html`
<sl-tooltip
content=${msg(
"Default settings for all new crawl workflows. Existing workflows will not be affected.",
)}
>
<sl-icon
class="text-base text-neutral-500"
name="info-circle"
></sl-icon>
</sl-tooltip>
`,
})}
<btrix-org-settings-crawling-defaults></btrix-org-settings-crawling-defaults>
</btrix-tab-panel>
</btrix-tab-list>`;
</btrix-tab-group-panel>
</btrix-tab-group>`;
}
private renderPanelHeader({
title,
actions,
}: {
title: string;
actions?: TemplateResult;
}) {
return html`
<header class="mb-2 flex items-center justify-between">
<h3 class="text-lg font-medium">${title}</h3>
${actions}
</header>
`;
}
private renderTab(name: Tab, path: string) {
const isActive = name === this.activePanel;
return html`
<btrix-navigation-button
<btrix-tab-group-tab
slot="nav"
panel=${name}
href=${`${this.navigate.orgBasePath}/${path}`}
.active=${isActive}
@click=${this.navigate.link}
aria-selected=${isActive}
>
${choose(name, [
[
@ -250,7 +259,7 @@ export class OrgSettings extends BtrixElement {
],
])}
${this.tabLabels[name]}
</btrix-navigation-button>
</btrix-tab-group-tab>
`;
}

View File

@ -12,7 +12,6 @@ import type { Crawl, Seed, Workflow, WorkflowParams } from "./types";
import { BtrixElement } from "@/classes/BtrixElement";
import type { PageChangeEvent } from "@/components/ui/pagination";
import { type IntersectEvent } from "@/components/utils/observable";
import { ClipboardController } from "@/controllers/clipboard";
import type { CrawlLog } from "@/features/archived-items/crawl-logs";
import { CrawlStatus } from "@/features/archived-items/crawl-status";
@ -113,8 +112,6 @@ export class WorkflowDetail extends BtrixElement {
private timerId?: number;
private isPanelHeaderVisible?: boolean;
private getWorkflowPromise?: Promise<Workflow>;
private getSeedsPromise?: Promise<APIPaginatedList<Seed>>;
@ -180,13 +177,6 @@ export class WorkflowDetail extends BtrixElement {
changedProperties.has("activePanel") &&
this.activePanel
) {
if (!this.isPanelHeaderVisible) {
// Scroll panel header into view
this.querySelector("btrix-tab-list")?.scrollIntoView({
behavior: "smooth",
});
}
if (this.activePanel === "crawls") {
void this.fetchCrawls();
}
@ -456,22 +446,20 @@ export class WorkflowDetail extends BtrixElement {
}
private readonly renderTabList = () => html`
<btrix-tab-list activePanel=${ifDefined(this.activePanel)} hideIndicator>
<btrix-observable
slot="header"
@intersect=${({ detail }: IntersectEvent) =>
(this.isPanelHeaderVisible = detail.entry.isIntersecting)}
<btrix-tab-group active=${ifDefined(this.activePanel)} placement="start">
<header
class="mb-2 flex h-7 items-center justify-between text-lg font-medium"
>
<header class="flex h-5 items-center justify-between">
${this.renderPanelHeader()}
</header>
</btrix-observable>
${this.renderPanelHeader()}
</header>
${this.renderTab("crawls")} ${this.renderTab("watch")}
${this.renderTab("logs")} ${this.renderTab("settings")}
<btrix-tab-panel name="crawls">${this.renderCrawls()}</btrix-tab-panel>
<btrix-tab-panel name="watch">
<btrix-tab-group-panel name="crawls">
${this.renderCrawls()}
</btrix-tab-group-panel>
<btrix-tab-group-panel name="watch">
${until(
this.getWorkflowPromise?.then(
() => html`
@ -486,12 +474,14 @@ export class WorkflowDetail extends BtrixElement {
`,
),
)}
</btrix-tab-panel>
<btrix-tab-panel name="logs">${this.renderLogs()}</btrix-tab-panel>
<btrix-tab-panel name="settings">
</btrix-tab-group-panel>
<btrix-tab-group-panel name="logs">
${this.renderLogs()}
</btrix-tab-group-panel>
<btrix-tab-group-panel name="settings">
${this.renderSettings()}
</btrix-tab-panel>
</btrix-tab-list>
</btrix-tab-group-panel>
</btrix-tab-group>
`;
private renderPanelHeader() {
@ -582,11 +572,11 @@ export class WorkflowDetail extends BtrixElement {
private renderTab(tabName: Tab, { disabled = false } = {}) {
const isActive = tabName === this.activePanel;
return html`
<btrix-navigation-button
<btrix-tab-group-tab
slot="nav"
panel=${tabName}
href=${`${window.location.pathname}#${tabName}`}
.active=${isActive}
.disabled=${disabled}
?disabled=${disabled}
aria-selected=${isActive}
aria-disabled=${disabled}
@click=${(e: MouseEvent) => {
@ -603,7 +593,7 @@ export class WorkflowDetail extends BtrixElement {
["settings", () => html`<sl-icon name="file-code-fill"></sl-icon>`],
])}
${this.tabLabels[tabName]}
</btrix-navigation-button>
</btrix-tab-group-tab>
`;
}