feat: Enable viewing all workflow form sections at once (#2310)

- Displays workflow form as collapsible sections
- Combines run now toggle into submit
- Fixes exclusion field errors not preventing form submission
- Refactors `<btrix-observable>` into new `Observable` controller

---------

Co-authored-by: emma <hi@emma.cafe>
This commit is contained in:
sua yoo 2025-02-04 12:56:36 -08:00 committed by GitHub
parent 83211b2f19
commit 18e72262dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 769 additions and 737 deletions

View File

@ -1,4 +1,3 @@
import clsx from "clsx";
import { html, type PropertyValues } from "lit";
import {
customElement,
@ -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:
@ -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";
@ -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() {

View File

@ -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>
* ```
*/
const tabTagName = "btrix-tab-list-tab" as const;
@customElement("btrix-tab-panel")
export class TabPanel extends TailwindElement {
@property({ type: String })
name?: string;
@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;
@ -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)}
@ -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 {
@ -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;
@ -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}) {
@ -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 })
@ -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"));
@ -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() {
@ -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,
);
}
@ -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) {
@ -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");
}
});
}
}

View File

@ -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>`;
}
}

View File

@ -0,0 +1,50 @@
import type { ReactiveController, ReactiveControllerHost } from "lit";
type IntersectionEventDetail = {
entries: IntersectionObserverEntry[];
};
export type IntersectEvent = CustomEvent<IntersectionEventDetail>;
/**
* Observe one or more elements with Intersection Observer API.
*
* @fires btrix-intersect IntersectionEventDetail
*/
export class ObservableController implements ReactiveController {
private readonly host: ReactiveControllerHost & EventTarget;
private observer?: IntersectionObserver;
private readonly observerOptions?: IntersectionObserverInit;
constructor(
host: ObservableController["host"],
options?: IntersectionObserverInit,
) {
this.host = host;
this.observerOptions = options;
host.addController(this);
}
hostConnected() {
this.observer = new IntersectionObserver(
this.handleIntersect,
this.observerOptions,
);
}
hostDisconnected() {
this.observer?.disconnect();
}
public observe(target: Element) {
this.observer?.observe(target);
}
private readonly handleIntersect = (entries: IntersectionObserverEntry[]) => {
this.host.dispatchEvent(
new CustomEvent<IntersectionEventDetail>("btrix-intersect", {
detail: { entries },
}),
);
};
}

View File

@ -10,6 +10,7 @@ import { when } from "lit/directives/when.js";
import throttle from "lodash/fp/throttle";
import { BtrixElement } from "@/classes/BtrixElement";
import type { IntersectEvent } from "@/controllers/observable";
type Pages = string[];
type ResponseData = {
@ -201,7 +202,7 @@ export class CrawlQueue extends BtrixElement {
${msg("End of queue")}
</div>`,
() => html`
<btrix-observable @intersect=${this.onLoadMoreIntersect}>
<btrix-observable @btrix-intersect=${this.onLoadMoreIntersect}>
<div class="py-3">
<sl-icon-button
name="three-dots"
@ -232,8 +233,8 @@ export class CrawlQueue extends BtrixElement {
`;
}
private readonly onLoadMoreIntersect = throttle(50)((e: CustomEvent) => {
if (!e.detail.entry.isIntersecting) return;
private readonly onLoadMoreIntersect = throttle(50)((e: IntersectEvent) => {
if (!e.detail.entries[0].isIntersecting) return;
this.loadMore();
}) as (e: CustomEvent) => void;

View File

@ -1,6 +1,8 @@
import { localized, msg, str } from "@lit/localize";
import type { SlInput, SlSelect } from "@shoelace-style/shoelace";
import clsx from "clsx";
import { css, html, type PropertyValues, type TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { customElement, property, queryAll, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { when } from "lit/directives/when.js";
import { html as staticHtml, unsafeStatic } from "lit/static-html.js";
@ -12,18 +14,17 @@ import { TailwindElement } from "@/classes/TailwindElement";
import { type PageChangeEvent } from "@/components/ui/pagination";
import type { SeedConfig } from "@/pages/org/types";
import { regexEscape, regexUnescape } from "@/utils/string";
import { tw } from "@/utils/tailwind";
export type ExclusionChangeEvent = CustomEvent<{
export type ExclusionChangeEventDetail = {
index: number;
regex: string;
}>;
valid?: boolean;
};
export type ExclusionRemoveEvent = CustomEvent<{
index: number;
regex: string;
}>;
export type ExclusionChangeEvent = CustomEvent<ExclusionChangeEventDetail>;
type SLInputElement = HTMLInputElement & { invalid: boolean };
export type ExclusionRemoveEvent = CustomEvent<ExclusionChangeEventDetail>;
const MIN_LENGTH = 2;
@ -54,12 +55,6 @@ function formatValue(type: Exclusion["type"], value: Exclusion["value"]) {
@localized()
export class QueueExclusionTable extends TailwindElement {
static styles = css`
sl-input {
--sl-input-border-radius-medium: 0;
--sl-input-font-family: var(--sl-font-mono);
--sl-input-spacing-medium: var(--sl-spacing-small);
}
sl-input:not([data-invalid]) {
--sl-input-border-width: 0;
}
@ -100,6 +95,19 @@ export class QueueExclusionTable extends TailwindElement {
@state()
private exclusionToRemove?: string;
@queryAll("sl-input")
private readonly inputs!: NodeListOf<SlInput>;
public reportValidity() {
this.inputs.forEach((input) => {
input.reportValidity();
});
}
public checkValidity() {
return ![...this.inputs].some((input) => !input.validity.valid);
}
private get total() {
return this.exclusions?.length;
}
@ -187,11 +195,11 @@ export class QueueExclusionTable extends TailwindElement {
${msg("Exclusion Value")}
</th>
<th class="${actionColClass} w-10 bg-slate-50 px-2 font-normal">
<span class="sr-only">Row actions</span>
<span class="sr-only">${msg("Row actions")}</span>
</th>
</tr>
</thead>
<tbody>
<tbody class="align-top">
${this.results.map(this.renderItem)}
</tbody>
</table>
@ -220,12 +228,12 @@ export class QueueExclusionTable extends TailwindElement {
<tr
class="${this.exclusionToRemove === exclusion.value
? "text-neutral-200"
: "text-neutral-600"} h-10"
: "text-neutral-600"}"
>
<td class="${typeColClass} whitespace-nowrap">
${this.renderType({ exclusion, index })}
</td>
<td class="${valueColClass} font-mono">
<td class="${valueColClass}">
${this.renderValue({ exclusion, index })}
</td>
<td class="${actionColClass} text-center text-[1rem]">
@ -266,11 +274,23 @@ export class QueueExclusionTable extends TailwindElement {
return html`
<sl-select
placeholder=${msg("Select Type")}
class="my-1"
size="small"
.value=${exclusion.type}
value=${exclusion.type}
@sl-hide=${this.stopProp}
@sl-after-hide=${this.stopProp}
@sl-change=${(e: Event) => {
const inputElem = (e.target as SlSelect)
.closest("tr")
?.querySelector("sl-input");
if (inputElem) {
this.checkInputValidity(inputElem);
this.reportInputValidity(inputElem);
} else {
console.debug("no inputElem for ", e.target);
}
void this.updateExclusion({
type: (e.target as HTMLSelectElement).value as Exclusion["type"],
value: exclusion.value,
@ -302,7 +322,13 @@ export class QueueExclusionTable extends TailwindElement {
<sl-input
name="exclusion-${index}"
placeholder=${msg("Enter value")}
class="m-0"
class=${clsx(
tw`m-0`,
tw`[--sl-input-border-radius-medium:0] [--sl-input-spacing-medium:var(--sl-spacing-small)]`,
tw`part-[form-control-help-text]:mx-1 part-[form-control-help-text]:mb-1`,
exclusion.type === "regex" &&
tw`[--sl-input-font-family:var(--sl-font-mono)]`,
)}
value=${exclusion.value}
autocomplete="off"
autocorrect="off"
@ -315,16 +341,16 @@ export class QueueExclusionTable extends TailwindElement {
});
}}
@sl-input=${(e: CustomEvent) => {
const inputElem = e.target as SLInputElement;
const validityMessage = this.getInputValidity(inputElem) || "";
inputElem.classList.remove("invalid");
inputElem.setCustomValidity(validityMessage);
const inputElem = e.target as SlInput;
this.checkInputValidity(inputElem);
this.checkSiblingRowValidity(e);
}}
@sl-change=${(e: CustomEvent) => {
const inputElem = e.target as SLInputElement;
const inputElem = e.target as SlInput;
this.reportInputValidity(inputElem);
const values = this.getCurrentValues(inputElem);
const params = {
type: values.type || exclusion.type,
@ -332,11 +358,6 @@ export class QueueExclusionTable extends TailwindElement {
index,
};
if (!inputElem.checkValidity()) {
inputElem.classList.add("invalid");
}
inputElem.reportValidity();
void this.updateExclusion(params);
}}
></sl-input>
@ -344,7 +365,7 @@ export class QueueExclusionTable extends TailwindElement {
}
if (exclusion.type === "regex") {
value = staticHtml`<span class="regex">${unsafeStatic(
value = staticHtml`<span class="regex ${tw`font-mono`}">${unsafeStatic(
new RegexColorize().colorizeText(exclusion.value) as string,
)}</span>`;
}
@ -352,6 +373,27 @@ export class QueueExclusionTable extends TailwindElement {
return value;
}
private checkInputValidity(inputElem: SlInput) {
const validityMessage = this.getInputValidity(inputElem) || "";
inputElem.setCustomValidity(validityMessage);
if (inputElem.classList.contains("invalid")) {
// Update help text on change
this.reportInputValidity(inputElem);
}
}
private reportInputValidity(inputElem: SlInput) {
if (inputElem.validationMessage) {
inputElem.classList.add("invalid");
} else {
inputElem.classList.remove("invalid");
}
inputElem.helpText = inputElem.validationMessage;
}
private getColumnClassNames(
index: number,
count: number,
@ -398,7 +440,7 @@ export class QueueExclusionTable extends TailwindElement {
return [typeColClass, valueColClass, actionColClass];
}
private getCurrentValues(inputElem: SLInputElement) {
private getCurrentValues(inputElem: SlInput) {
// Get latest exclusion type value from select
const typeSelectElem = inputElem.closest("tr")?.querySelector("sl-select");
const exclusionType = typeSelectElem?.value;
@ -408,7 +450,7 @@ export class QueueExclusionTable extends TailwindElement {
};
}
private getInputDuplicateValidity(inputElem: SLInputElement) {
private getInputDuplicateValidity(inputElem: SlInput) {
const siblingElems = inputElem
.closest("table")
?.querySelectorAll(`sl-input:not([name="${inputElem.name}"])`);
@ -417,7 +459,7 @@ export class QueueExclusionTable extends TailwindElement {
return;
}
const siblingValues = Array.from(siblingElems).map(
(elem) => (elem as SLInputElement).value,
(elem) => (elem as SlInput).value,
);
const { type, value } = this.getCurrentValues(inputElem);
const formattedValue = formatValue(type!, value);
@ -426,10 +468,24 @@ export class QueueExclusionTable extends TailwindElement {
}
}
private getInputValidity(inputElem: SLInputElement): string | void {
private getInputValidity(inputElem: SlInput): string | void {
const { type, value } = this.getCurrentValues(inputElem);
if (!value) return;
const validityMessage = this.getValidityMessage({ type, value });
if (validityMessage) return validityMessage;
return this.getInputDuplicateValidity(inputElem);
}
private getValidityMessage({
type,
value,
}: {
type?: Exclusion["type"];
value: string;
}) {
if (value.length < MIN_LENGTH) {
return msg(str`Please enter ${MIN_LENGTH} or more characters`);
}
@ -439,13 +495,9 @@ export class QueueExclusionTable extends TailwindElement {
// Check if valid regex
new RegExp(value);
} catch (err) {
return msg(
"Please enter a valid Regular Expression constructor pattern",
);
return msg("Please enter a valid regular expression");
}
}
return this.getInputDuplicateValidity(inputElem);
}
private checkSiblingRowValidity(e: CustomEvent) {
@ -456,9 +508,8 @@ export class QueueExclusionTable extends TailwindElement {
Array.from(table.querySelectorAll("sl-input[data-invalid]")).map((elem) => {
if (elem !== inputElem) {
const validityMessage =
this.getInputDuplicateValidity(elem as SLInputElement) || "";
(elem as SLInputElement).setCustomValidity(validityMessage);
(elem as SLInputElement).reportValidity();
this.getInputDuplicateValidity(elem as SlInput) || "";
(elem as SlInput).setCustomValidity(validityMessage);
}
});
}
@ -477,11 +528,18 @@ export class QueueExclusionTable extends TailwindElement {
await this.updateComplete;
let valid: boolean | undefined;
if (value.length) {
valid = !this.getValidityMessage({ type, value });
}
this.dispatchEvent(
new CustomEvent("btrix-remove", {
new CustomEvent<ExclusionChangeEventDetail>("btrix-remove", {
detail: {
index,
regex,
valid,
},
}) as ExclusionRemoveEvent,
);
@ -517,13 +575,20 @@ export class QueueExclusionTable extends TailwindElement {
await this.updateComplete;
let valid: boolean | undefined;
if (value.length) {
valid = !this.getValidityMessage({ type, value });
}
this.dispatchEvent(
new CustomEvent("btrix-change", {
new CustomEvent<ExclusionChangeEventDetail>("btrix-change", {
detail: {
index,
regex,
valid,
},
}) as ExclusionChangeEvent,
}),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
import clsx from "clsx";
import { html, type TemplateResult } from "lit";
import { tw } from "@/utils/tailwind";
export function pageSectionsWithNav({
nav,
main,
placement = "start",
sticky = false,
}: {
nav: TemplateResult;
main: TemplateResult;
placement?: "start" | "top";
sticky?: boolean;
}) {
return html`
<div
class=${clsx(
tw`flex flex-col`,
placement === "start" && tw`gap-8 lg:flex-row`,
)}
>
<div
class=${clsx(
tw`flex flex-1 flex-col gap-2`,
sticky && tw`lg:sticky lg:top-2 lg:self-start`,
placement === "start" ? tw`lg:max-w-[16.5rem]` : tw`lg:flex-row`,
)}
>
${nav}
</div>
<div class="flex-1">${main}</div>
</div>
`;
}

View File

@ -0,0 +1,44 @@
import { html, type TemplateResult } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import { pageHeading } from "./page";
export function panelHeader({
heading,
actions,
}: {
heading: string | Parameters<typeof pageHeading>[0];
actions?: TemplateResult;
}) {
return html`
<header class="mb-3 flex min-h-8 items-baseline justify-between">
${typeof heading === "string"
? pageHeading({ content: heading })
: pageHeading(heading)}
${actions}
</header>
`;
}
export function panelBody({ content }: { content: TemplateResult }) {
return html`<div class="lg:rounded-lg lg:border lg:p-4">${content}</div>`;
}
/**
* @TODO Refactor components to use panel
*/
export function panel({
heading,
actions,
body,
id,
className,
}: {
body: TemplateResult;
id?: string;
className?: string;
} & Parameters<typeof panelHeader>[0]) {
return html`<section id=${ifDefined(id)} class=${ifDefined(className)}>
${panelHeader({ heading, actions })} ${body}
</section>`;
}

View File

@ -6,8 +6,9 @@ import { type FormState } from "@/utils/workflow";
type Field = keyof FormState;
const infoText: Partial<Record<Field, string | TemplateResult>> = {
exclusions: msg(`Specify exclusion rules for what pages should not be visited.
Exclusions apply to all URLs.`),
exclusions: msg(
"Specify exclusion rules for what pages should not be visited.",
),
pageLimit: msg(
"Adds a hard limit on the number of pages that will be crawled.",
),

View File

@ -183,11 +183,13 @@
}
/* Validation styles */
[data-user-invalid]:not([disabled])::part(base) {
sl-input[data-user-invalid]:not([disabled])::part(base),
sl-textarea[data-user-invalid]:not([disabled])::part(base) {
border-color: var(--sl-color-danger-400);
}
[data-user-invalid]:focus-within::part(base) {
sl-input[data-user-invalid]:focus-within::part(base),
sl-textarea[data-user-invalid]:focus-within::part(base) {
box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-100);
}

View File

@ -155,9 +155,6 @@ export function getInitialFormState(params: {
org?: OrgData | null;
}): FormState {
const defaultFormState = getDefaultFormState();
if (!params.configId) {
defaultFormState.runNow = true;
}
if (!params.initialWorkflow) return defaultFormState;
const formState: Partial<FormState> = {};
const seedsConfig = params.initialWorkflow.config;