Skip to content

Conversation

syedszeeshan
Copy link
Collaborator

@syedszeeshan syedszeeshan commented Sep 18, 2025

Before (the change)

After (the change)

Make sure that you've checked the boxes below before you submit the PR

  • I have read and followed the setup steps
  • I have created necessary unit tests
  • I have tested the functionality in both React and Angular.

Steps needed to test

Playground Code
Angular

angular-issue-2054.html.txt
angular-issue-2054.ts.txt

image image image

React

import React, { useState } from "react";
import {
  GoabTooltip,
  GoabIcon,
  GoabFormItem,
  GoabTextArea,
  GoabDropdown,
  GoabDropdownItem,
  GoabButton,
} from "@abgov/react-components";
import {
  GoabTextAreaOnChangeDetail,
  GoabDropdownOnChangeDetail,
} from "@abgov/ui-components-common";
import "@abgov/style";

const countries = [
  { code: "CA", name: "Canada" },
  { code: "US", name: "United States" },
  { code: "MX", name: "Mexico" },
  { code: "GB", name: "United Kingdom" },
  { code: "FR", name: "France" },
  { code: "DE", name: "Germany" },
  { code: "JP", name: "Japan" },
  { code: "CN", name: "China" },
  { code: "AU", name: "Australia" },
  { code: "BR", name: "Brazil" },
];

const fruits = [
  { value: "apple", label: "Apple" },
  { value: "banana", label: "Banana" },
  { value: "cherry", label: "Cherry" },
  { value: "date", label: "Date" },
  { value: "elderberry", label: "Elderberry" },
  { value: "fig", label: "Fig" },
  { value: "grape", label: "Grape" },
  { value: "honeydew", label: "Honeydew" },
];

const longOptions = [
  {
    value: "option1",
    label: "Very Long Option Name That Would Exceed Normal Width Constraints",
  },
  {
    value: "option2",
    label: "Another Extremely Long Option Name for Testing MaxWidth Functionality",
  },
  {
    value: "option3",
    label: "Super Lengthy Option Description That Demonstrates Width Wrapping",
  },
  { value: "option4", label: "Extended Option Label to Test Dropdown Width Management" },
  {
    value: "option5",
    label: "Comprehensive Long Option Name for MaxWidth Validation Testing",
  },
];

export const Issue2054Page: React.FC = () => {
  const longTextExample =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante.";

  // TextArea state
  const [wideTextArea, setWideTextArea] = useState(longTextExample);
  const [shortTextArea, setShortTextArea] = useState("");
  const [maxWidthOnlyTextArea, setMaxWidthOnlyTextArea] = useState("");

  // Dropdown state
  const [wideDropdown, setWideDropdown] = useState<string | undefined>();
  const [shortDropdown, setShortDropdown] = useState<string | undefined>();
  const [maxWidthOnlyDropdown, setMaxWidthOnlyDropdown] = useState<string | undefined>();
  const [longOptionsDropdown, setLongOptionsDropdown] = useState<string | undefined>();

  const resetAllExamples = () => {
    setWideTextArea(longTextExample);
    setShortTextArea("");
    setMaxWidthOnlyTextArea("");
    setWideDropdown(undefined);
    setShortDropdown(undefined);
    setMaxWidthOnlyDropdown(undefined);
    setLongOptionsDropdown(undefined);
  };

  return (
    <div style={{ padding: "12px" }}>
      <h1>Issue #2054: MaxWidth Support for Tooltip, TextArea, and Dropdown</h1>

      <div
        style={{
          padding: "12px",
          background: "#e3f2fd",
          borderRadius: "4px",
          fontSize: "14px",
          marginBottom: "24px",
          borderLeft: "4px solid #2196f3",
        }}
      >
        <strong>Issue Description:</strong> This page demonstrates the implementation of
        maxWidth support for three UI components: Tooltip, TextArea, and Dropdown. The
        maxWidth property constrains component width while preserving all existing
        functionality.
      </div>

      {/* Tooltip Examples */}
      <section
        style={{
          marginBottom: "40px",
          padding: "20px",
          border: "1px solid #ddd",
          borderRadius: "8px",
        }}
      >
        <h2 style={{ marginTop: 0, marginBottom: "16px", color: "#333" }}>
          Tooltip MaxWidth Examples
        </h2>

        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "16px",
            marginBottom: "12px",
          }}
        >
          <GoabTooltip
            content="This is a very long tooltip content that would normally extend far beyond reasonable limits, but with maxWidth set to 200px it should wrap nicely and stay constrained within the specified maximum width boundary. This demonstrates how the maxWidth property helps create more manageable and user-friendly tooltips."
            maxWidth="200px"
          >
            <GoabIcon type="information-circle" />
          </GoabTooltip>
          <span>Long content with maxWidth=200px</span>
        </div>

        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "16px",
            marginBottom: "12px",
          }}
        >
          <GoabTooltip
            maxWidth="300px"
            content={
              <div style={{ color: "white" }}>
                <div
                  style={{ fontWeight: "bold", marginBottom: "8px", color: "#ffeb3b" }}
                >
                  Performance Notice
                </div>
                <p style={{ margin: "0 0 8px 0", lineHeight: 1.4 }}>
                  This operation processes large datasets and may take several minutes to
                  complete. Please ensure you have a stable internet connection and avoid
                  navigating away from this page.
                </p>
                <div style={{ fontSize: "12px", color: "#e0e0e0" }}>
                  <strong>Tip:</strong> You can minimize this window and continue working
                  on other tasks while the process runs in the background.
                </div>
              </div>
            }
          >
            <GoabIcon type="help-circle" />
          </GoabTooltip>
          <span>Rich content with maxWidth=300px</span>
        </div>

        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: "16px",
            marginBottom: "12px",
          }}
        >
          <GoabTooltip content="Short content" maxWidth="400px">
            <GoabIcon type="information-circle" />
          </GoabTooltip>
          <span>Short content with maxWidth=400px (natural width)</span>
        </div>
      </section>

      {/* TextArea Examples */}
      <section
        style={{
          marginBottom: "40px",
          padding: "20px",
          border: "1px solid #ddd",
          borderRadius: "8px",
        }}
      >
        <h2 style={{ marginTop: 0, marginBottom: "16px", color: "#333" }}>
          TextArea MaxWidth Examples
        </h2>

        <GoabFormItem
          label="MaxWidth with wide initial width"
          labelSize="regular"
          helpText="Width: 800px, MaxWidth: 300px - The textarea should be constrained to 300px width"
        >
          <GoabTextArea
            name="wideTextArea"
            width="800px"
            maxWidth="300px"
            placeholder="This textarea has width=800px but maxWidth=300px, so it should be constrained to 300px maximum width"
            value={wideTextArea}
            onChange={(detail: GoabTextAreaOnChangeDetail) =>
              setWideTextArea((detail.value as string) || "")
            }
          />
        </GoabFormItem>

        <GoabFormItem
          label="MaxWidth with shorter content"
          labelSize="regular"
          helpText="Width: 200px, MaxWidth: 400px - Should use the 200px width since it's smaller"
        >
          <GoabTextArea
            name="shortTextArea"
            width="200px"
            maxWidth="400px"
            placeholder="Short content with maxWidth larger than width"
            value={shortTextArea}
            onChange={(detail: GoabTextAreaOnChangeDetail) =>
              setShortTextArea((detail.value as string) || "")
            }
          />
        </GoabFormItem>

        <GoabFormItem
          label="MaxWidth only (no width specified)"
          labelSize="regular"
          helpText="MaxWidth: 250px - Should grow naturally but not exceed 250px"
        >
          <GoabTextArea
            name="maxWidthOnlyTextArea"
            maxWidth="250px"
            placeholder="This textarea only has maxWidth=250px set, with no explicit width"
            value={maxWidthOnlyTextArea}
            onChange={(detail: GoabTextAreaOnChangeDetail) =>
              setMaxWidthOnlyTextArea((detail.value as string) || "")
            }
          />
        </GoabFormItem>
      </section>

      {/* Dropdown Examples */}
      <section
        style={{
          marginBottom: "40px",
          padding: "20px",
          border: "1px solid #ddd",
          borderRadius: "8px",
        }}
      >
        <h2 style={{ marginTop: 0, marginBottom: "16px", color: "#333" }}>
          Dropdown MaxWidth Examples
        </h2>

        <GoabFormItem
          label="MaxWidth with wide initial width"
          labelSize="regular"
          helpText="Width: 800px, MaxWidth: 300px - Should be constrained to 300px maximum width"
        >
          <GoabDropdown
            name="wide-dropdown"
            width="800px"
            maxWidth="300px"
            placeholder="Width: 800px, MaxWidth: 300px"
            value={wideDropdown}
            onChange={(detail: GoabDropdownOnChangeDetail) =>
              setWideDropdown((detail.value as string) || undefined)
            }
            filterable
          >
            {countries.slice(0, 10).map((c) => (
              <GoabDropdownItem key={c.code} value={c.code} label={c.name} />
            ))}
          </GoabDropdown>
        </GoabFormItem>

        <GoabFormItem
          label="MaxWidth with shorter width"
          labelSize="regular"
          helpText="Width: 200px, MaxWidth: 400px - Should use 200px width since it's smaller"
        >
          <GoabDropdown
            name="short-dropdown"
            width="200px"
            maxWidth="400px"
            placeholder="Width: 200px, MaxWidth: 400px"
            value={shortDropdown}
            onChange={(detail: GoabDropdownOnChangeDetail) =>
              setShortDropdown((detail.value as string) || undefined)
            }
          >
            <GoabDropdownItem value="option1" label="Option 1" />
            <GoabDropdownItem value="option2" label="Option 2" />
            <GoabDropdownItem value="option3" label="Option 3" />
          </GoabDropdown>
        </GoabFormItem>

        <GoabFormItem
          label="MaxWidth only (no width specified)"
          labelSize="regular"
          helpText="MaxWidth: 250px - Should grow naturally but not exceed 250px"
        >
          <GoabDropdown
            name="maxwidth-only"
            maxWidth="250px"
            placeholder="MaxWidth: 250px only"
            value={maxWidthOnlyDropdown}
            onChange={(detail: GoabDropdownOnChangeDetail) =>
              setMaxWidthOnlyDropdown((detail.value as string) || undefined)
            }
            filterable
          >
            {fruits.map((fruit) => (
              <GoabDropdownItem
                key={fruit.value}
                value={fruit.value}
                label={fruit.label}
              />
            ))}
          </GoabDropdown>
        </GoabFormItem>

        <GoabFormItem
          label="Filterable with long options and maxWidth"
          labelSize="regular"
          helpText="MaxWidth: 320px with long option names - Tests dropdown and popover width constraints"
        >
          <GoabDropdown
            name="long-options"
            maxWidth="320px"
            placeholder="Search long option names..."
            value={longOptionsDropdown}
            onChange={(detail: GoabDropdownOnChangeDetail) =>
              setLongOptionsDropdown((detail.value as string) || undefined)
            }
            filterable
          >
            {longOptions.map((opt) => (
              <GoabDropdownItem key={opt.value} value={opt.value} label={opt.label} />
            ))}
          </GoabDropdown>
        </GoabFormItem>
      </section>

      <div style={{ textAlign: "center", margin: "20px 0" }}>
        <GoabButton onClick={resetAllExamples}>Reset All Examples</GoabButton>
      </div>
    </div>
  );
};

export default Issue2054Page;

@syedszeeshan syedszeeshan force-pushed the Syed/2054-UI branch 2 times, most recently from 23cb3de to 95ff993 Compare September 19, 2025 17:32
@syedszeeshan syedszeeshan changed the title NOT READY -fix(#2054): add maxwidth prop to dropdown, textarea and tooltip fix(#2054): add maxwidth prop to dropdown, textarea and tooltip Sep 19, 2025
@syedszeeshan syedszeeshan marked this pull request as ready for review September 19, 2025 17:43
@syedszeeshan syedszeeshan linked an issue Sep 19, 2025 that may be closed by this pull request
@syedszeeshan syedszeeshan force-pushed the Syed/2054-UI branch 2 times, most recently from 069e025 to 0ab5690 Compare September 22, 2025 20:23
@syedszeeshan
Copy link
Collaborator Author

-Added React playground code, and rebased with alpha
-Build is failing (the flaky browser tests)...will keep trying

width: width.includes("%") ? width : `min(${width}, 100%)`,
});
const finalWidth = width.includes("%") ? width : `min(${width}, 100%)`;
const cssProps: Record<string, string> = { width: finalWidth };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a slight issue. width and max-width and min-width are all being applied simultaneously in different spots. You're applying width, and max-width here in this onMount.

  1. width is also applied on line 172 inside the div root
  2. max-width is also applied on line 230 inside the CSS for .root
  3. min-width and max-width are also being applied on lines 283 and 284 inside the CSS for textarea
  4. min-width and max-width are also being applied on lines 329, 330, and 336, 337 inside the CSS for mobile and for not-mobile

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be because of above, I'm not sure. But TextArea width set to a percentage value doesn't work as expected.

// Public
export let content = "";
export let testid: string = "";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltip using maxWidth with maxWidth set to a percentage doesn't display accurately

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ch also doesn't work as expected:
maxWidth set to 10ch:
image

maxWidth set to 100ch:
image

Using this code:

<goab-block direction="column" gap="m">
  <goab-text tag="h2">Tooltip examples</goab-text>
  <goab-grid minChildWidth="260px" gap="m">
    <goab-block
      *ngFor="let example of tooltipExamples; index as index"
      direction="column"
      gap="s"
    >
      <goab-text tag="h3">{{ example.label }}</goab-text>
      <goab-text tag="p">{{ example.description }}</goab-text>
      <goab-tooltip
        [content]="example.content"
        [maxWidth]="example.maxWidth"
        [style.width]="example.width"
      >
        <goab-icon type="information-circle" size="3"></goab-icon>
      </goab-tooltip>
    </goab-block>
  </goab-grid>
</goab-block>

readonly tooltipExamples: TooltipExample[] = [
  {
    label: "Default tooltip",
    description: "Relies on the default tooltip sizing for comparison.",
    content:
      "This tooltip keeps the default sizing so longer text can expand without additional constraints.",
  },
  {
    label: "Style width 500px, maxWidth 200px",
    description: "Applies an inline width while capping the tooltip content at 200px.",
    width: "500px",
    maxWidth: "200px",
    content:
      "Inline width is set to 500px but the tooltip maxWidth is restricted to 200px to demonstrate clamping.",
  },
  {
    label: "Max width 300px",
    description: "Limits the tooltip content to 300px without changing width.",
    maxWidth: "100ch",
    content:
      "The tooltip content should wrap within 300px, demonstrating the maxWidth attribute in pixels.",
  },
  {
    label: "Max width 50%",
    description: "Uses a responsive maxWidth to scale with the parent container.",
    width: "500px",
    maxWidth: "100%",
    content:
      "This tooltip uses a percentage based maxWidth so it will shrink or grow with its container width.",
  },
];

export let value: string | undefined = "";
export let filterable: string = "false";
export let leadingicon: GoAIconType | null = null;
export let maxheight: string = "276px";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting maxWidth to a percentage doesn't work the same as width to a percentage.
maxWidth to 50%:
image

width to 50%:
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR updated and angular playground code updated also.

describe("Dropdown Component", () => {
const noop = () => {
// noop
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all the browser tests, you're only testing basic px measurements. You should also have tests using % and ch to make sure those work as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR updated.

const spaceLeft = rootRect.left;
const spaceRight = window.innerWidth - rootRect.right;
const calculatedMaxWidth = maxwidth ? parseFloat(maxwidth) : 400;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work in the case that a user specifies a non-px value. Since the calculation is required to determine which side the tooltip will appear on, for now let's restrict them to using px values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR updated.

export let rows: number = 3;
export let testid: string = "";
export let width: string = "60ch";
export let maxwidth: string = "";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent the current width default property from conflicting with the maxwidth, change to the following

export let width: string = "100%"
export let maxwidth: string = "60ch"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently when setting the maxWidth it behaves like width, in that if the screen is resized the component stays set at the max width (this is true for both the textarea and dropdown), but it would be assumed by devs that the component would either be at 100% or the maxwidth.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR updated.

@syedszeeshan syedszeeshan marked this pull request as draft September 23, 2025 22:17
@syedszeeshan syedszeeshan marked this pull request as ready for review September 24, 2025 18:23
@syedszeeshan syedszeeshan marked this pull request as draft October 6, 2025 18:28
@syedszeeshan syedszeeshan changed the title fix(#2054): add maxwidth prop to dropdown, textarea and tooltip DONT-REVIEW-TEST Oct 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants