Skip to content

Commit 22a33b4

Browse files
authored
feat: Add tag constraints (#3006)
- Limits tag input to 40 characters - Determines maximum tags displayed by container width
1 parent 37acae9 commit 22a33b4

File tree

9 files changed

+204
-23
lines changed

9 files changed

+204
-23
lines changed

frontend/src/components/ui/badge.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export type BadgeVariant =
1313
| "primary"
1414
| "cyan"
1515
| "blue"
16-
| "high-contrast";
16+
| "high-contrast"
17+
| "text";
1718

1819
/**
1920
* Show numeric value in a label
@@ -64,6 +65,7 @@ export class Badge extends TailwindElement {
6465
primary: tw`bg-white text-primary ring-primary`,
6566
cyan: tw`bg-cyan-50 text-cyan-600 ring-cyan-600`,
6667
blue: tw`bg-blue-50 text-blue-600 ring-blue-600`,
68+
text: tw`text-blue-500 ring-blue-600`,
6769
}[this.variant],
6870
]
6971
: {
@@ -75,6 +77,7 @@ export class Badge extends TailwindElement {
7577
primary: tw`bg-primary text-neutral-0`,
7678
cyan: tw`bg-cyan-50 text-cyan-600`,
7779
blue: tw`bg-blue-50 text-blue-600`,
80+
text: tw`text-blue-500`,
7881
}[this.variant],
7982
this.pill
8083
? [

frontend/src/components/ui/code/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ export class Code extends TailwindElement {
5353
}
5454
5555
.hljs-path {
56-
color: var(--sl-color-blue-900);
56+
color: var(--sl-color-sky-600);
5757
}
5858
5959
.hljs-domain {
60-
color: var(--sl-color-blue-600);
60+
color: var(--sl-color-sky-700);
6161
}
6262
6363
.hljs-string {

frontend/src/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import("./combobox");
1616
import("./config-details");
1717
import("./copy-button");
1818
import("./copy-field");
19+
import("./tag-container");
1920
import("./data-grid");
2021
import("./details");
2122
import("./file-input");
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import clsx from "clsx";
2+
import { css, html, type PropertyValues } from "lit";
3+
import {
4+
customElement,
5+
property,
6+
query,
7+
queryAll,
8+
state,
9+
} from "lit/decorators.js";
10+
import { ifDefined } from "lit/directives/if-defined.js";
11+
import debounce from "lodash/fp/debounce";
12+
13+
import { TailwindElement } from "@/classes/TailwindElement";
14+
import type { Tag } from "@/components/ui/tag";
15+
import type { UnderlyingFunction } from "@/types/utils";
16+
import localize from "@/utils/localize";
17+
import { tw } from "@/utils/tailwind";
18+
19+
/**
20+
* Displays all the tags that can be contained to one line.
21+
* Overflowing tags are displayed in a popover.
22+
*
23+
* @cssproperty width
24+
*/
25+
@customElement("btrix-tag-container")
26+
export class TagContainer extends TailwindElement {
27+
static styles = css`
28+
:host {
29+
--width: 100%;
30+
}
31+
`;
32+
33+
@property({ type: Array })
34+
tags: string[] = [];
35+
36+
@query("#container")
37+
private readonly container?: HTMLElement | null;
38+
39+
@queryAll("btrix-tag")
40+
private readonly tagNodes!: NodeListOf<Tag>;
41+
42+
@state()
43+
private displayLimit?: number;
44+
45+
disconnectedCallback(): void {
46+
this.debouncedCalculate.cancel();
47+
super.disconnectedCallback();
48+
}
49+
50+
protected updated(changedProperties: PropertyValues): void {
51+
if (changedProperties.get("tags")) {
52+
this.debouncedCalculate.cancel();
53+
this.calculate();
54+
}
55+
}
56+
57+
render() {
58+
const maxTags = this.tags.length;
59+
const displayLimit = this.displayLimit;
60+
const remainder = displayLimit && maxTags - displayLimit;
61+
62+
return html`
63+
<sl-resize-observer
64+
@sl-resize=${this.debouncedCalculate as UnderlyingFunction<
65+
typeof this.calculate
66+
>}
67+
>
68+
<div class="flex items-center">
69+
<div
70+
id="container"
71+
class="flex h-6 w-[var(--width)] flex-wrap gap-x-1.5 overflow-hidden contain-content"
72+
>
73+
${this.tags.map(
74+
(tag, i) =>
75+
html`<btrix-tag
76+
aria-hidden=${ifDefined(
77+
displayLimit === undefined
78+
? undefined
79+
: i > displayLimit - 1
80+
? "true"
81+
: "false",
82+
)}
83+
>${tag}</btrix-tag
84+
>`,
85+
)}
86+
</div>
87+
88+
<btrix-popover hoist placement="right">
89+
<btrix-badge
90+
variant="text"
91+
size="large"
92+
class=${clsx(!remainder && tw`invisible`)}
93+
aria-hidden=${remainder ? "false" : "true"}
94+
tabIndex="0"
95+
>+${localize.number(remainder || maxTags)}</btrix-badge
96+
>
97+
<div slot="content" class="z-50 flex flex-wrap gap-1.5">
98+
${this.tags
99+
.slice(displayLimit)
100+
.map((tag) => html`<btrix-tag>${tag}</btrix-tag>`)}
101+
</div>
102+
</btrix-popover>
103+
</div>
104+
</sl-resize-observer>
105+
`;
106+
}
107+
108+
private readonly calculate = () => {
109+
const tagNodes = Array.from(this.tagNodes);
110+
111+
if (!tagNodes.length || !this.container) return;
112+
113+
const containerRect = this.container.getBoundingClientRect();
114+
const containerTop = containerRect.top;
115+
116+
// Reset width
117+
this.style.setProperty("--width", "100%");
118+
const idx = tagNodes.findIndex(
119+
(el) => el.getBoundingClientRect().top > containerTop,
120+
);
121+
122+
if (idx === -1) return;
123+
const lastVisible = tagNodes[idx - 1];
124+
if (lastVisible as unknown) {
125+
const rect = lastVisible.getBoundingClientRect();
126+
// Decrease width of container to match end of last visible tag
127+
this.style.setProperty(
128+
"--width",
129+
`${rect.left - containerRect.left + rect.width}px`,
130+
);
131+
}
132+
133+
this.displayLimit = idx;
134+
};
135+
136+
private readonly debouncedCalculate = debounce(50)(this.calculate);
137+
}

frontend/src/components/ui/tag-input.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
import { customElement, property, query, state } from "lit/decorators.js";
1717
import debounce from "lodash/fp/debounce";
1818

19+
import { TAG_MAX_CHARACTERS } from "./tag";
20+
1921
import type { UnderlyingFunction } from "@/types/utils";
2022
import { type WorkflowTag } from "@/types/workflow";
2123
import { dropdown } from "@/utils/css";
@@ -234,6 +236,7 @@ export class TagInput extends LitElement {
234236
role="combobox"
235237
aria-controls="dropdown"
236238
aria-expanded="${this.dropdownIsOpen === true}"
239+
maxlength=${TAG_MAX_CHARACTERS}
237240
/>
238241
<div
239242
id="dropdown"

frontend/src/components/ui/tag.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { css, html } from "lit";
33
import { customElement, property } from "lit/decorators.js";
44
import { ifDefined } from "lit/directives/if-defined.js";
55

6+
export const TAG_MAX_CHARACTERS = 40;
7+
68
/**
79
* Customized <sl-tag>
810
*
@@ -55,7 +57,8 @@ export class Tag extends SLTag {
5557
}
5658
5759
.tag__content {
58-
max-width: 100%;
60+
display: inline-block;
61+
max-width: ${TAG_MAX_CHARACTERS + 2}ch;
5962
white-space: nowrap;
6063
overflow: hidden;
6164
text-overflow: ellipsis;

frontend/src/pages/org/browser-profiles-list.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,11 @@ const DEFAULT_SORT_BY = {
6666
} as const satisfies SortBy;
6767
const INITIAL_PAGE_SIZE = 20;
6868
const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawls";
69-
const MAX_TAGS = 3;
7069

7170
const columnsCss = [
7271
"min-content", // Status
7372
"[clickable-start] minmax(min-content, 1fr)", // Name
74-
"minmax(max-content, 1fr)", // Tags
73+
"30ch", // Tags
7574
"minmax(max-content, 1fr)", // Origins
7675
"minmax(min-content, 20ch)", // Last modified
7776
"[clickable-end] min-content", // Actions
@@ -523,8 +522,6 @@ export class BrowserProfilesList extends BtrixElement {
523522
) || data.created;
524523
const startingUrl = data.origins[0];
525524
const otherOrigins = data.origins.slice(1);
526-
const firstTags = data.tags.slice(0, MAX_TAGS);
527-
const otherTags = data.tags.slice(MAX_TAGS);
528525

529526
return html`
530527
<btrix-table-row
@@ -550,25 +547,14 @@ export class BrowserProfilesList extends BtrixElement {
550547
>
551548
</btrix-table-cell>
552549
<btrix-table-cell>
553-
<div class="flex flex-wrap gap-1.5">
554-
${firstTags.map((tag) => html`<btrix-tag>${tag}</btrix-tag>`)}
555-
</div>
556-
${otherTags.length
557-
? html`<btrix-popover placement="right" hoist>
558-
<btrix-badge
559-
>+${this.localize.number(otherTags.length)}</btrix-badge
560-
>
561-
<div slot="content" class="flex flex-wrap gap-1.5">
562-
${otherTags.map((tag) => html`<btrix-tag>${tag}</btrix-tag>`)}
563-
</div>
564-
</btrix-popover>`
565-
: nothing}
550+
<btrix-tag-container class="relative hover:z-[2]" .tags=${data.tags}>
551+
</btrix-tag-container>
566552
</btrix-table-cell>
567-
<btrix-table-cell>
553+
<btrix-table-cell class="[--btrix-table-cell-gap:0]">
568554
<btrix-code language="url" value=${startingUrl} noWrap></btrix-code>
569555
${otherOrigins.length
570556
? html`<btrix-popover placement="right" hoist>
571-
<btrix-badge
557+
<btrix-badge variant="text" size="large"
572558
>+${this.localize.number(otherOrigins.length)}</btrix-badge
573559
>
574560
<ul slot="content">
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Meta, StoryObj } from "@storybook/web-components";
2+
3+
import { renderComponent, type RenderProps } from "./TagContainer";
4+
5+
const meta = {
6+
title: "Components/Tag Container",
7+
component: "btrix-contain-with-remainder",
8+
tags: ["autodocs"],
9+
render: renderComponent,
10+
argTypes: {},
11+
} satisfies Meta<RenderProps>;
12+
13+
export default meta;
14+
type Story = StoryObj<RenderProps>;
15+
16+
/**
17+
* Resize your browser window to see the remaining tags update.
18+
*/
19+
export const Basic: Story = {
20+
args: {
21+
tags: [
22+
"Social Media",
23+
"Marketing",
24+
"Tooling",
25+
"High Priority",
26+
"Low Priority",
27+
"Dev",
28+
"Approved",
29+
"Rejected",
30+
"Good",
31+
"Bad",
32+
"2024",
33+
"2025",
34+
"2026",
35+
],
36+
},
37+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { html } from "lit";
2+
3+
import type { TagContainer } from "@/components/ui/tag-container";
4+
5+
import "@/components/ui/tag-container";
6+
7+
export type RenderProps = TagContainer;
8+
9+
export const renderComponent = ({ tags }: Partial<RenderProps>) => {
10+
return html`<btrix-tag-container .tags=${tags || []}></btrix-tag-container>`;
11+
};

0 commit comments

Comments
 (0)