Skip to content

Commit 1029f83

Browse files
syedszeeshanchrisolsen
authored andcommitted
fix(#2054): add maxwidth prop to dropdown, textarea and tooltip
1 parent c446c1b commit 1029f83

File tree

18 files changed

+944
-132
lines changed

18 files changed

+944
-132
lines changed

libs/angular-components/src/lib/components/dropdown/dropdown.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { fireEvent } from "@testing-library/dom";
2121
[testId]="testId"
2222
[id]="id"
2323
[width]="width"
24+
[maxWidth]="maxWidth"
2425
[mt]="mt"
2526
[mr]="mr"
2627
[mb]="mb"
@@ -56,6 +57,7 @@ class TestDropdownComponent {
5657
placeholder?: string;
5758
testId?: string;
5859
width?: string;
60+
maxWidth?: string;
5961
mt?: Spacing;
6062
mb?: Spacing;
6163
ml?: Spacing;
@@ -93,6 +95,7 @@ describe("GoABDropdown", () => {
9395
component.testId = "foo";
9496
component.id = "foo-dropdown";
9597
component.width = "200px";
98+
component.maxWidth = "400px";
9699
component.mt = "s";
97100
component.mr = "m";
98101
component.mb = "l";
@@ -115,6 +118,7 @@ describe("GoABDropdown", () => {
115118
expect(el?.getAttribute("arialabel")).toBe("Label");
116119
expect(el?.getAttribute("arialabelledby")).toBe("foo-dropdown-label");
117120
expect(el?.getAttribute("autocomplete")).toBe("off");
121+
expect(el?.getAttribute("maxwidth")).toBe("400px");
118122

119123
// Check options
120124
const dropdownItems = el.querySelectorAll("goa-dropdown-item");
@@ -145,4 +149,4 @@ describe("GoABDropdown", () => {
145149
);
146150
expect(onChangeMock).toHaveBeenCalled();
147151
});
148-
});
152+
});

libs/angular-components/src/lib/components/dropdown/dropdown.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { GoabControlValueAccessor } from "../base.component";
3535
[attr.placeholder]="placeholder"
3636
[attr.testid]="testId"
3737
[attr.width]="width"
38+
[attr.maxwidth]="maxWidth"
3839
[attr.relative]="relative"
3940
[attr.autocomplete]="autoComplete"
4041
[id]="id"
@@ -63,6 +64,7 @@ export class GoabDropdown extends GoabControlValueAccessor {
6364
@Input({ transform: booleanAttribute }) native?: boolean;
6465
@Input() placeholder?: string;
6566
@Input() width?: string;
67+
@Input() maxWidth?: string;
6668
@Input() autoComplete?: string;
6769
/***
6870
* @deprecated This property has no effect and will be removed in a future version
@@ -78,4 +80,4 @@ export class GoabDropdown extends GoabControlValueAccessor {
7880
this.markAsTouched();
7981
this.fcChange?.(detail.value || "");
8082
}
81-
}
83+
}

libs/angular-components/src/lib/components/tooltip/tooltip.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { GoabIcon } from "../icon/icon";
88
<goab-tooltip content="This is a tooltip" position="top" hAlign="right" testId="foo">
99
<goab-icon type="information-circle"></goab-icon>
1010
</goab-tooltip>
11+
<goab-tooltip content="This is a tooltip" maxWidth="300px" testId="foo-maxwidth">
12+
<goab-icon type="information-circle"></goab-icon>
13+
</goab-tooltip>
1114
`,
1215
})
1316
class TestTooltipComponent {
@@ -28,7 +31,7 @@ describe("GoABTooltip", () => {
2831
});
2932

3033
it("should render", () => {
31-
const el = fixture.nativeElement.querySelector("goa-tooltip");
34+
const el = fixture.nativeElement.querySelector('goa-tooltip[testid="foo"]');
3235
expect(el?.getAttribute("content")).toBe("This is a tooltip");
3336
expect(el?.getAttribute("position")).toBe("top");
3437
expect(el?.getAttribute("halign")).toBe("right");
@@ -37,4 +40,10 @@ describe("GoABTooltip", () => {
3740
const goaIcon = el?.querySelector("goa-icon");
3841
expect(goaIcon?.getAttribute("type")).toBe("information-circle");
3942
});
40-
});
43+
44+
it("should render with maxWidth property", () => {
45+
const el = fixture.nativeElement.querySelector('goa-tooltip[testid="foo-maxwidth"]');
46+
expect(el?.getAttribute("maxwidth")).toBe("300px");
47+
expect(el?.getAttribute("testid")).toBe("foo-maxwidth");
48+
});
49+
});

libs/angular-components/src/lib/components/tooltip/tooltip.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { GoabBaseComponent } from "../base.component";
1515
[attr.content]="getContentAsString()"
1616
[attr.halign]="hAlign"
1717
[attr.testid]="testId"
18+
[attr.maxwidth]="maxWidth"
1819
[attr.mt]="mt"
1920
[attr.mb]="mb"
2021
[attr.ml]="ml"
@@ -37,6 +38,7 @@ export class GoabTooltip extends GoabBaseComponent {
3738
@Input() position?: GoabTooltipPosition;
3839
@Input() content?: string | TemplateRef<unknown>;
3940
@Input() hAlign?: GoabTooltipHorizontalAlignment;
41+
@Input() maxWidth?: string;
4042

4143
getContentAsString(): string {
4244
return this.content instanceof TemplateRef ? "" : this.content || "";
@@ -46,4 +48,4 @@ export class GoabTooltip extends GoabBaseComponent {
4648
if (!this.content) return null;
4749
return this.content instanceof TemplateRef ? this.content : null;
4850
}
49-
}
51+
}

libs/react-components/specs/dropdown.browser.spec.tsx

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,218 @@ describe("Dropdown", () => {
136136
expect(computedStyle.width).toBe("300px");
137137
});
138138
});
139+
140+
it("uses maxWidth value as width when width prop not supplied", async () => {
141+
const Component = () => (
142+
<GoabDropdown
143+
name="favcolor"
144+
testId="favcolor-maxonly"
145+
maxWidth="320px"
146+
onChange={noop}
147+
>
148+
<GoabDropdownItem label="Red" value="red" />
149+
<GoabDropdownItem label="Blue" value="blue" />
150+
<GoabDropdownItem label="Green" value="green" />
151+
</GoabDropdown>
152+
);
153+
154+
const result = render(<Component />);
155+
const dropdown = result.getByTestId("favcolor-maxonly");
156+
157+
await vi.waitFor(() => {
158+
const styleAttr = dropdown.element().getAttribute("style") || "";
159+
// internal logic sets --width to maxWidth when width not provided
160+
expect(styleAttr).toContain("--width: 320px");
161+
const computedStyle = window.getComputedStyle(dropdown.element());
162+
expect(computedStyle.width).toBe("320px");
163+
});
164+
});
165+
166+
it("ignores maxwidth when width prop is provided", async () => {
167+
const Component = () => (
168+
<GoabDropdown
169+
name="favcolor"
170+
testId="favcolor-width-wins"
171+
width="800px"
172+
maxWidth="320px"
173+
onChange={noop}
174+
>
175+
<GoabDropdownItem label="Red" value="red" />
176+
<GoabDropdownItem label="Blue" value="blue" />
177+
<GoabDropdownItem label="Green" value="green" />
178+
</GoabDropdown>
179+
);
180+
181+
const result = render(<Component />);
182+
const dropdown = result.getByTestId("favcolor-width-wins");
183+
await vi.waitFor(() => {
184+
const styleAttr = dropdown.element().getAttribute("style") || "";
185+
expect(styleAttr).toContain("--width: 800px");
186+
const computedStyle = window.getComputedStyle(dropdown.element());
187+
expect(parseFloat(computedStyle.width)).toBeGreaterThan(320);
188+
});
189+
});
190+
191+
it("caps natural width with percentage maxWidth (container-controlled)", async () => {
192+
const Component = () => (
193+
<div style={{ width: "600px" }} data-testid="container-600">
194+
<GoabDropdown
195+
name="favcolor"
196+
testId="percentage-maxwidth-dropdown"
197+
maxWidth="50%"
198+
onChange={noop}
199+
>
200+
<GoabDropdownItem
201+
label="Extremely Long Option Label To Grow Width"
202+
value="long"
203+
/>
204+
<GoabDropdownItem label="Blue" value="blue" />
205+
<GoabDropdownItem label="Green" value="green" />
206+
</GoabDropdown>
207+
</div>
208+
);
209+
210+
const result = render(<Component />);
211+
const dropdown = result.getByTestId("percentage-maxwidth-dropdown");
212+
const container = result.getByTestId("container-600");
213+
214+
await vi.waitFor(() => {
215+
const containerWidth = parseFloat(
216+
window.getComputedStyle(container.element()).width,
217+
);
218+
const computedWidth = parseFloat(
219+
window.getComputedStyle(dropdown.element()).width,
220+
);
221+
// Target ~300px (50% of 600) within tolerance
222+
expect(computedWidth).toBeGreaterThan(250);
223+
expect(computedWidth).toBeLessThan(310);
224+
expect(Math.abs(computedWidth - containerWidth * 0.5)).toBeLessThanOrEqual(15);
225+
});
226+
});
227+
228+
it("caps natural width with character (ch) maxWidth", async () => {
229+
const Component = () => (
230+
<GoabDropdown
231+
name="favcolor"
232+
testId="ch-maxwidth-dropdown"
233+
maxWidth="25ch"
234+
onChange={noop}
235+
>
236+
<GoabDropdownItem label="Red" value="red" />
237+
<GoabDropdownItem label="Blue" value="blue" />
238+
<GoabDropdownItem label="Green" value="green" />
239+
</GoabDropdown>
240+
);
241+
242+
const result = render(<Component />);
243+
const dropdown = result.getByTestId("ch-maxwidth-dropdown");
244+
245+
await vi.waitFor(() => {
246+
const pxWidth = parseFloat(window.getComputedStyle(dropdown.element()).width);
247+
expect(pxWidth).toBeGreaterThan(50);
248+
expect(pxWidth).toBeLessThan(600);
249+
});
250+
});
251+
252+
it("supports percentage width units", async () => {
253+
const Component = () => {
254+
return (
255+
<GoabDropdown
256+
name="favcolor"
257+
testId="percentage-dropdown"
258+
width="75%"
259+
onChange={noop}
260+
>
261+
<GoabDropdownItem label="Red" value="red" />
262+
<GoabDropdownItem label="Blue" value="blue" />
263+
<GoabDropdownItem label="Green" value="green" />
264+
</GoabDropdown>
265+
);
266+
};
267+
268+
const result = render(<Component />);
269+
const dropdown = result.getByTestId("percentage-dropdown");
270+
271+
await vi.waitFor(() => {
272+
// Check that width is set with percentage unit
273+
const styleAttr = dropdown.element().getAttribute("style") || "";
274+
expect(styleAttr).toContain("--width: 75%");
275+
276+
// Check computed width is percentage of container
277+
const computedStyle = window.getComputedStyle(dropdown.element());
278+
expect(computedStyle.width).toMatch(/^\d+(\.\d+)?px$/); // Should be converted to pixels
279+
280+
// Check that it's a reasonable percentage width (should be substantial but not too large)
281+
const dropdownWidth = parseFloat(computedStyle.width);
282+
expect(dropdownWidth).toBeGreaterThan(100); // Should be substantial
283+
expect(dropdownWidth).toBeLessThan(800); // But not too large for 75%
284+
});
285+
});
286+
287+
it("supports character (ch) width units", async () => {
288+
const Component = () => {
289+
return (
290+
<GoabDropdown
291+
name="favcolor"
292+
testId="ch-dropdown"
293+
width="30ch"
294+
onChange={noop}
295+
>
296+
<GoabDropdownItem label="Red" value="red" />
297+
<GoabDropdownItem label="Blue" value="blue" />
298+
<GoabDropdownItem label="Green" value="green" />
299+
</GoabDropdown>
300+
);
301+
};
302+
303+
const result = render(<Component />);
304+
const dropdown = result.getByTestId("ch-dropdown");
305+
306+
await vi.waitFor(() => {
307+
// Check that width is set with ch unit
308+
const styleAttr = dropdown.element().getAttribute("style") || "";
309+
expect(styleAttr).toContain("--width: 30ch");
310+
311+
// Check computed width is applied
312+
const computedStyle = window.getComputedStyle(dropdown.element());
313+
expect(computedStyle.width).toMatch(/^\d+(\.\d+)?px$/); // Browser converts ch to px
314+
315+
// Should have a reasonable width (ch is approximately font width)
316+
const dropdownWidth = parseFloat(computedStyle.width);
317+
expect(dropdownWidth).toBeGreaterThan(200); // Should be substantial width
318+
expect(dropdownWidth).toBeLessThan(600); // But not too large
319+
});
320+
});
321+
322+
it("defaults to px when no unit is provided", async () => {
323+
const Component = () => {
324+
return (
325+
<GoabDropdown
326+
name="favcolor"
327+
testId="no-unit-dropdown"
328+
width="250"
329+
onChange={noop}
330+
>
331+
<GoabDropdownItem label="Red" value="red" />
332+
<GoabDropdownItem label="Blue" value="blue" />
333+
<GoabDropdownItem label="Green" value="green" />
334+
</GoabDropdown>
335+
);
336+
};
337+
338+
const result = render(<Component />);
339+
const dropdown = result.getByTestId("no-unit-dropdown");
340+
341+
await vi.waitFor(() => {
342+
// Check that width is converted to px when no unit provided
343+
const styleAttr = dropdown.element().getAttribute("style") || "";
344+
expect(styleAttr).toContain("--width: 250px");
345+
346+
// Check computed width matches expected px value
347+
const computedStyle = window.getComputedStyle(dropdown.element());
348+
expect(computedStyle.width).toBe("250px");
349+
});
350+
});
139351
});
140352

141353
describe("Popover position", () => {

0 commit comments

Comments
 (0)