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:
		
							parent
							
								
									a028ed1808
								
							
						
					
					
						commit
						c563b622fe
					
				| @ -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}> | ||||
|       <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> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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 }) | ||||
|  | ||||
| @ -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") | ||||
|  | ||||
| @ -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` | ||||
|  | ||||
| @ -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> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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>`;
 | ||||
|  | ||||
| @ -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,15 +160,24 @@ 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> | ||||
|       <btrix-tab-group active=${this.activePanel} placement="start"> | ||||
|         ${this.renderTab("information", "settings")} | ||||
|         ${this.renderTab("members", "settings/members")} | ||||
|         ${when(this.appState.settings?.billingEnabled, () => | ||||
|           this.renderTab("billing", "settings/billing"), | ||||
|         )} | ||||
|         ${this.renderTab("crawling-defaults", "settings/crawling-defaults")} | ||||
| 
 | ||||
|         <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-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" | ||||
| @ -178,13 +193,19 @@ export class OrgSettings extends BtrixElement { | ||||
|                 ${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")} | ||||
|           })} | ||||
|           ${this.renderMembers()} | ||||
|         </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-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.", | ||||
| @ -195,47 +216,35 @@ export class OrgSettings extends BtrixElement { | ||||
|                   name="info-circle" | ||||
|                 ></sl-icon> | ||||
|               </sl-tooltip> | ||||
|                   </h3>`,
 | ||||
|               ], | ||||
|             ], | ||||
|             () => html`<h3>${this.tabLabels[this.activePanel]}</h3>`, | ||||
|           )} | ||||
|         </header> | ||||
|         ${this.renderTab("information", "settings")} | ||||
|         ${this.renderTab("members", "settings/members")} | ||||
|         ${when(this.appState.settings?.billingEnabled, () => | ||||
|           this.renderTab("billing", "settings/billing"), | ||||
|         )} | ||||
|         ${this.renderTab("crawling-defaults", "settings/crawling-defaults")} | ||||
| 
 | ||||
|         <btrix-tab-panel name="information"> | ||||
|           ${this.renderInformation()} | ||||
|           <btrix-org-settings-profile></btrix-org-settings-profile> | ||||
|           ${this.renderApi()} | ||||
|         </btrix-tab-panel> | ||||
|         <btrix-tab-panel name="members"> | ||||
|           ${this.renderMembers()} | ||||
|         </btrix-tab-panel> | ||||
|         <btrix-tab-panel name="billing"> | ||||
|           <btrix-org-settings-billing | ||||
|             .salesEmail=${this.appState.settings?.salesEmail} | ||||
|           ></btrix-org-settings-billing> | ||||
|         </btrix-tab-panel> | ||||
|         <btrix-tab-panel name="crawling-defaults"> | ||||
|             `,
 | ||||
|           })} | ||||
|           <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> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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.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> | ||||
|     `;
 | ||||
|   } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user