diff --git a/frontend/src/components/ui/badge.ts b/frontend/src/components/ui/badge.ts index 16bb776352..adc17f2b12 100644 --- a/frontend/src/components/ui/badge.ts +++ b/frontend/src/components/ui/badge.ts @@ -13,7 +13,8 @@ export type BadgeVariant = | "primary" | "cyan" | "blue" - | "high-contrast"; + | "high-contrast" + | "text"; /** * Show numeric value in a label @@ -64,6 +65,7 @@ export class Badge extends TailwindElement { primary: tw`bg-white text-primary ring-primary`, cyan: tw`bg-cyan-50 text-cyan-600 ring-cyan-600`, blue: tw`bg-blue-50 text-blue-600 ring-blue-600`, + text: tw`text-blue-500 ring-blue-600`, }[this.variant], ] : { @@ -75,6 +77,7 @@ export class Badge extends TailwindElement { primary: tw`bg-primary text-neutral-0`, cyan: tw`bg-cyan-50 text-cyan-600`, blue: tw`bg-blue-50 text-blue-600`, + text: tw`text-blue-500`, }[this.variant], this.pill ? [ diff --git a/frontend/src/components/ui/code/index.ts b/frontend/src/components/ui/code/index.ts index 24f13919ea..2955bcd095 100644 --- a/frontend/src/components/ui/code/index.ts +++ b/frontend/src/components/ui/code/index.ts @@ -53,11 +53,11 @@ export class Code extends TailwindElement { } .hljs-path { - color: var(--sl-color-blue-900); + color: var(--sl-color-sky-600); } .hljs-domain { - color: var(--sl-color-blue-600); + color: var(--sl-color-sky-700); } .hljs-string { diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 9c631de3a0..632598bc7c 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -16,6 +16,7 @@ import("./combobox"); import("./config-details"); import("./copy-button"); import("./copy-field"); +import("./tag-container"); import("./data-grid"); import("./details"); import("./file-input"); diff --git a/frontend/src/components/ui/tag-container.ts b/frontend/src/components/ui/tag-container.ts new file mode 100644 index 0000000000..eea43fb312 --- /dev/null +++ b/frontend/src/components/ui/tag-container.ts @@ -0,0 +1,137 @@ +import clsx from "clsx"; +import { css, html, type PropertyValues } from "lit"; +import { + customElement, + property, + query, + queryAll, + state, +} from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import debounce from "lodash/fp/debounce"; + +import { TailwindElement } from "@/classes/TailwindElement"; +import type { Tag } from "@/components/ui/tag"; +import type { UnderlyingFunction } from "@/types/utils"; +import localize from "@/utils/localize"; +import { tw } from "@/utils/tailwind"; + +/** + * Displays all the tags that can be contained to one line. + * Overflowing tags are displayed in a popover. + * + * @cssproperty width + */ +@customElement("btrix-tag-container") +export class TagContainer extends TailwindElement { + static styles = css` + :host { + --width: 100%; + } + `; + + @property({ type: Array }) + tags: string[] = []; + + @query("#container") + private readonly container?: HTMLElement | null; + + @queryAll("btrix-tag") + private readonly tagNodes!: NodeListOf; + + @state() + private displayLimit?: number; + + disconnectedCallback(): void { + this.debouncedCalculate.cancel(); + super.disconnectedCallback(); + } + + protected updated(changedProperties: PropertyValues): void { + if (changedProperties.get("tags")) { + this.debouncedCalculate.cancel(); + this.calculate(); + } + } + + render() { + const maxTags = this.tags.length; + const displayLimit = this.displayLimit; + const remainder = displayLimit && maxTags - displayLimit; + + return html` + } + > +
+
+ ${this.tags.map( + (tag, i) => + html` displayLimit - 1 + ? "true" + : "false", + )} + >${tag}`, + )} +
+ + + +${localize.number(remainder || maxTags)} +
+ ${this.tags + .slice(displayLimit) + .map((tag) => html`${tag}`)} +
+
+
+
+ `; + } + + private readonly calculate = () => { + const tagNodes = Array.from(this.tagNodes); + + if (!tagNodes.length || !this.container) return; + + const containerRect = this.container.getBoundingClientRect(); + const containerTop = containerRect.top; + + // Reset width + this.style.setProperty("--width", "100%"); + const idx = tagNodes.findIndex( + (el) => el.getBoundingClientRect().top > containerTop, + ); + + if (idx === -1) return; + const lastVisible = tagNodes[idx - 1]; + if (lastVisible as unknown) { + const rect = lastVisible.getBoundingClientRect(); + // Decrease width of container to match end of last visible tag + this.style.setProperty( + "--width", + `${rect.left - containerRect.left + rect.width}px`, + ); + } + + this.displayLimit = idx; + }; + + private readonly debouncedCalculate = debounce(50)(this.calculate); +} diff --git a/frontend/src/components/ui/tag-input.ts b/frontend/src/components/ui/tag-input.ts index 9540b1993e..0260270a04 100644 --- a/frontend/src/components/ui/tag-input.ts +++ b/frontend/src/components/ui/tag-input.ts @@ -16,6 +16,8 @@ import { import { customElement, property, query, state } from "lit/decorators.js"; import debounce from "lodash/fp/debounce"; +import { TAG_MAX_CHARACTERS } from "./tag"; + import type { UnderlyingFunction } from "@/types/utils"; import { type WorkflowTag } from "@/types/workflow"; import { dropdown } from "@/utils/css"; @@ -234,6 +236,7 @@ export class TagInput extends LitElement { role="combobox" aria-controls="dropdown" aria-expanded="${this.dropdownIsOpen === true}" + maxlength=${TAG_MAX_CHARACTERS} />