Repository: jpmorganchase/salt-ds
Pull request #4822: Fix status adornment alignment
Author: login: origami-z
Created: 2025-03-11T10:19:05Z | Merged: 2025-03-28T09:43:47Z

Use [`?path=/story/experimental-kitchen-sink--form-status-alignment`](https://jpmorganchase-4794-status-ad.saltdesignsystem-storybook.pages.dev/?path=/story/experimental-kitchen-sink--form-status-alignment) to check alignment

#4794


Diff:
diff --git a/.changeset/famous-knives-tan.md b/.changeset/famous-knives-tan.md
new file mode 100644
index 00000000000..95e42a2bfe4
--- /dev/null
+++ b/.changeset/famous-knives-tan.md
@@ -0,0 +1,5 @@
+---
+"@salt-ds/lab": patch
+---
+
+Fixed DateInput adornment size and spacing, to align with rest of form controls.
diff --git a/.changeset/serious-cats-live.md b/.changeset/serious-cats-live.md
new file mode 100644
index 00000000000..fcc0ceb5ed4
--- /dev/null
+++ b/.changeset/serious-cats-live.md
@@ -0,0 +1,7 @@
+---
+"@salt-ds/core": patch
+---
+
+Fixed ComboBox and Dropdown status adornment spacing, to align with rest of form controls.
+
+Closes #4794
diff --git a/packages/core/src/combo-box/ComboBox.tsx b/packages/core/src/combo-box/ComboBox.tsx
index b610fdf344c..0f0eef333e0 100644
--- a/packages/core/src/combo-box/ComboBox.tsx
+++ b/packages/core/src/combo-box/ComboBox.tsx
@@ -66,7 +66,7 @@ export const ComboBox = forwardRef(function ComboBox<Item>(
     children,
     className,
     disabled: disabledProp,
-    endAdornment,
+    endAdornment: endAdornmentProp,
     readOnly: readOnlyProp,
     multiselect,
     selectOnTab = !multiselect,
@@ -399,6 +399,34 @@ export const ComboBox = forwardRef(function ComboBox<Item>(
 
   const handleListRef = useForkRef<HTMLDivElement>(listRef, floating);
 
+  const showOptionsButton = (
+    <Button
+      aria-labelledby={clsx(buttonId, formFieldLabelledBy)}
+      aria-label="Show options"
+      aria-expanded={openState}
+      aria-controls={openState ? listId : undefined}
+      aria-haspopup="listbox"
+      disabled={disabled}
+      appearance="transparent"
+      onClick={handleButtonClick}
+      onFocus={handleButtonFocus}
+      tabIndex={-1}
+    >
+      {openState ? <CollapseIcon aria-hidden /> : <ExpandIcon aria-hidden />}
+    </Button>
+  );
+
+  // avoid render empty fragment, or empty div appear in PillInput
+  const endAdornment =
+    !readOnly && hasValidChildren ? (
+      <>
+        {endAdornmentProp}
+        {showOptionsButton}
+      </>
+    ) : (
+      endAdornmentProp
+    );
+
   return (
     <ListControlContext.Provider value={listControl}>
       <PillInput
@@ -412,31 +440,7 @@ export const ComboBox = forwardRef(function ComboBox<Item>(
           },
           className,
         )}
-        endAdornment={
-          <>
-            {endAdornment}
-            {!readOnly && hasValidChildren ? (
-              <Button
-                aria-labelledby={clsx(buttonId, formFieldLabelledBy)}
-                aria-label="Show options"
-                aria-expanded={openState}
-                aria-controls={openState ? listId : undefined}
-                aria-haspopup="listbox"
-                disabled={disabled}
-                appearance="transparent"
-                onClick={handleButtonClick}
-                onFocus={handleButtonFocus}
-                tabIndex={-1}
-              >
-                {openState ? (
-                  <CollapseIcon aria-hidden />
-                ) : (
-                  <ExpandIcon aria-hidden />
-                )}
-              </Button>
-            ) : undefined}
-          </>
-        }
+        endAdornment={endAdornment}
         onChange={handleChange}
         // Workaround to have readonly conveyed by screen readers (https://github.com/jpmorganchase/salt-ds/issues/4586)
         role={readOnly ? "textbox" : "combobox"}
diff --git a/packages/core/src/dropdown/Dropdown.css b/packages/core/src/dropdown/Dropdown.css
index 3a9aff91950..dcb8b4cc1ad 100644
--- a/packages/core/src/dropdown/Dropdown.css
+++ b/packages/core/src/dropdown/Dropdown.css
@@ -11,7 +11,6 @@
   display: inline-flex;
   align-items: center;
   padding: 0 var(--salt-spacing-100);
-  gap: var(--salt-spacing-100);
   color: var(--dropdown-color);
   font-family: var(--salt-text-fontFamily);
   font-size: var(--salt-text-fontSize);
@@ -184,3 +183,11 @@
   color: var(--salt-content-secondary-foreground);
   font-weight: var(--salt-text-fontWeight-small);
 }
+
+.saltDropdown-startAdornmentContainer {
+  margin-right: var(--salt-spacing-100);
+}
+
+.saltDropdown .saltDropdown-toggle {
+  margin-left: var(--salt-spacing-100);
+}
diff --git a/packages/core/src/dropdown/Dropdown.tsx b/packages/core/src/dropdown/Dropdown.tsx
index 90a3bf20219..d2c54789bfe 100644
--- a/packages/core/src/dropdown/Dropdown.tsx
+++ b/packages/core/src/dropdown/Dropdown.tsx
@@ -97,13 +97,17 @@ export type DropdownProps<Item = string> = {
 } & Omit<ComponentPropsWithoutRef<"button">, "value" | "defaultValue"> &
   ListControlProps<Item>;
 
+const withBaseName = makePrefixer("saltDropdown");
+
 function ExpandIcon({ open }: { open: boolean }) {
   const { CollapseIcon, ExpandIcon } = useIcon();
-  return open ? <CollapseIcon aria-hidden /> : <ExpandIcon aria-hidden />;
+  return open ? (
+    <CollapseIcon className={withBaseName("toggle")} aria-hidden />
+  ) : (
+    <ExpandIcon className={withBaseName("toggle")} aria-hidden />
+  );
 }
 
-const withBaseName = makePrefixer("saltDropdown");
-
 export const Dropdown = forwardRef(function Dropdown<Item>(
   props: DropdownProps<Item>,
   ref: ForwardedRef<HTMLButtonElement>,
@@ -448,7 +452,11 @@ export const Dropdown = forwardRef(function Dropdown<Item>(
           ...rest,
         })}
       >
-        {startAdornment}
+        {startAdornment && (
+          <div className={withBaseName("startAdornmentContainer")}>
+            {startAdornment}
+          </div>
+        )}
         <span
           className={clsx(withBaseName("content"), {
             [withBaseName("placeholder")]: !valueText,
diff --git a/packages/core/src/pill-input/PillInput.css b/packages/core/src/pill-input/PillInput.css
index b33d89e7fd0..bebdd9a932a 100644
--- a/packages/core/src/pill-input/PillInput.css
+++ b/packages/core/src/pill-input/PillInput.css
@@ -204,7 +204,7 @@
 .saltPillInput-endAdornmentContainer {
   align-items: center;
   display: inline-flex;
-  padding-left: var(--salt-spacing-100);
+  padding-left: var(--salt-spacing-50);
   padding-top: var(--salt-spacing-50);
   column-gap: var(--salt-spacing-100);
   align-self: flex-start;
diff --git a/packages/lab/src/__tests__/__e2e__/tabs-next/TabsNext.cy.tsx b/packages/lab/src/__tests__/__e2e__/tabs-next/TabsNext.cy.tsx
index f38b5619a14..638486a7412 100644
--- a/packages/lab/src/__tests__/__e2e__/tabs-next/TabsNext.cy.tsx
+++ b/packages/lab/src/__tests__/__e2e__/tabs-next/TabsNext.cy.tsx
@@ -215,6 +215,9 @@ describe("Given a Tabstrip", () => {
     cy.findAllByRole("tab").filter(":visible").should("have.length", 1);
 
     cy.get("[data-overflowbutton]").realClick();
+    cy.findAllByRole("tab").filter(":visible").should("have.length", 14);
+
+    cy.findByRole("tab", { name: "Home" }).should("be.focused");
     cy.realPress("Enter");
     cy.findByRole("tab", { name: "Home" })
       .should("have.attr", "aria-selected", "true")
diff --git a/packages/lab/src/date-input/DateInput.css b/packages/lab/src/date-input/DateInput.css
index 7cec102040a..056af1d2a29 100644
--- a/packages/lab/src/date-input/DateInput.css
+++ b/packages/lab/src/date-input/DateInput.css
@@ -5,7 +5,6 @@
   border-radius: var(--salt-palette-corner-weak, 0);
   color: var(--saltDateInput-color, var(--salt-content-primary-foreground));
   display: inline-flex;
-  gap: var(--salt-spacing-50);
   font-family: var(--salt-text-fontFamily);
   font-size: var(--saltDateInput-fontSize, var(--salt-text-fontSize));
   line-height: var(--saltDateInput-lineHeight, var(--salt-text-lineHeight));
@@ -16,9 +15,9 @@
   padding-left: var(--saltDateInput-paddingLeft, var(--salt-spacing-100));
   padding-right: var(--saltDateInput-paddingRight, var(--salt-spacing-100));
   position: relative;
-  width: 100%;
   box-sizing: border-box;
   overflow: hidden;
+  width: 100%;
 }
 
 .saltDateInput:hover {
@@ -179,7 +178,7 @@
 }
 
 /* Style applied to start adornments */
-.saltInput-startAdornmentContainer {
+.saltDateInput-startAdornmentContainer {
   align-items: center;
   display: inline-flex;
   padding-right: var(--salt-spacing-100);
@@ -189,9 +188,7 @@
 /* Style applied to end adornments */
 .saltDateInput-endAdornmentContainer {
   display: inline-flex;
-  padding-left: var(--salt-spacing-100);
-  column-gap: var(--salt-spacing-100);
-  margin-left: auto;
+  column-gap: var(--salt-spacing-50);
   align-items: end;
 }
 
@@ -203,9 +200,11 @@
   margin-right: calc(var(--salt-spacing-50) * -1);
 }
 
+.saltDateInput-startAdornmentContainer > .saltButton,
 .saltDateInput-endAdornmentContainer > .saltButton {
-  --saltButton-padding: var(--salt-spacing-50);
+  --saltButton-padding: calc(var(--salt-spacing-50) - var(--salt-size-border));
   --saltButton-height: calc(var(--salt-size-base) - var(--salt-spacing-100));
+  --saltButton-borderRadius: var(--salt-palette-corner-weaker);
 }
 
 /* Style applied to inner input component */
@@ -225,6 +224,7 @@
   padding: 0;
   text-align: left;
   width: min-content;
+  width: 100%;
 }
 
 /* Reset in the  class */
@@ -247,3 +247,8 @@
   color: var(--salt-content-secondary-foreground);
   font-weight: var(--salt-text-fontWeight-small);
 }
+
+/* Style applied to the dash between two inputs in range date input */
+.saltDateInput-dash {
+  margin: 0 var(--salt-spacing-50);
+}
diff --git a/packages/lab/src/date-input/DateInputRange.tsx b/packages/lab/src/date-input/DateInputRange.tsx
index a46f166d79b..3de2ffecad9 100644
--- a/packages/lab/src/date-input/DateInputRange.tsx
+++ b/packages/lab/src/date-input/DateInputRange.tsx
@@ -554,7 +554,7 @@ export const DateInputRange = forwardRef<
           onFocus={!isDisabled ? handleStartInputFocus : undefined}
           required={startInputIsRequired}
         />
-        <span>-</span>
+        <span className={withBaseName("dash")}>-</span>
         <input
           autoComplete="off"
           aria-describedby={clsx(
diff --git a/packages/lab/stories/kitchen-sink/kitchen-sink.stories.tsx b/packages/lab/stories/kitchen-sink/kitchen-sink.stories.tsx
index c0c5c09bd82..9ea231e6130 100644
--- a/packages/lab/stories/kitchen-sink/kitchen-sink.stories.tsx
+++ b/packages/lab/stories/kitchen-sink/kitchen-sink.stories.tsx
@@ -2,18 +2,22 @@ import {
   Badge,
   Button,
   Card,
+  ComboBox,
   Dialog,
   DialogActions,
   DialogContent,
   DialogHeader,
   Display1,
+  Dropdown,
   FlexItem,
   FlexLayout,
   H1,
   H2,
   H3,
+  Input,
   Link,
   ListBox,
+  MultilineInput,
   Option,
   RadioButton,
   RadioButtonGroup,
@@ -77,6 +81,12 @@ import {
   HorizontalIconOnly as ToggleButtonGroupHorizontalIon,
   HorizontalTextOnly as ToggleButtonGroupHorizontalText,
 } from "../../../core/stories/toggle-button-group/toggle-button-group.stories";
+import {
+  DatePicker,
+  DatePickerSingleInput,
+  DatePickerTrigger,
+} from "../../src/date-picker/";
+import { NumberInput } from "../../src/number-input";
 
 import "ag-grid-community/styles/ag-grid.css";
 import "../../../../dist/salt-ds-ag-grid-theme/salt-ag-theme.css";
@@ -442,3 +452,29 @@ export const Example1 = () => {
     </StackLayout>
   );
 };
+
+export const FormStatusAlignment = () => {
+  return (
+    <StackLayout gap={1}>
+      <Input defaultValue="Input" validationStatus="error" />
+      <MultilineInput defaultValue="MultilineInput" validationStatus="error" />
+      <ComboBox value="Combo Box without Option" validationStatus="error" />
+      <ComboBox defaultValue="Combo Box with Option" validationStatus="error">
+        <Option value="1" />
+      </ComboBox>
+      <Dropdown value="Dropdown without Option" validationStatus="error" />
+      <Dropdown value="Dropdown with Option" validationStatus="error">
+        <Option value="1" />
+      </Dropdown>
+      <DatePicker selectionVariant="single">
+        <DatePickerTrigger>
+          <DatePickerSingleInput
+            defaultValue="bad date"
+            validationStatus="error"
+          />
+        </DatePickerTrigger>
+      </DatePicker>
+      <NumberInput defaultValue={0} validationStatus="error" />
+    </StackLayout>
+  );
+};


Reviews:
user: login: bhoppers2008
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T13:27:02Z
user: login: Fercas123
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T09:00:01Z
user: login: origami-z
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T09:08:13Z
user: login: yunjungyeh
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T09:22:12Z
user: login: mark-tate
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T10:06:02Z

Review comments:
diff_hunk: @@ -225,6 +224,7 @@
   padding: 0;
   text-align: left;
   width: min-content;
path: packages/lab/src/date-input/DateInput.css
user: login: Fercas123
body: ```suggestion
```
created_at: 2025-03-28T08:56:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,6 +224,7 @@
   padding: 0;
   text-align: left;
   width: min-content;
path: packages/lab/src/date-input/DateInput.css
user: login: origami-z
body: Will this fix more problems? cc @mark-tate 
created_at: 2025-03-28T09:08:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,6 +224,7 @@
   padding: 0;
   text-align: left;
   width: min-content;
path: packages/lab/src/date-input/DateInput.css
user: login: mark-tate
body: there are 2 width values in this style at present.
created_at: 2025-03-28T10:06:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-11T10:19:09Z
body: ###  🦋  Changeset detected

Latest commit: 411bcff2768e826660544353c182f443c812f45e

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 4 packages</summary>

  | Name               | Type  |
| ------------------ | ----- |
| @salt-ds/lab       | Patch |
| @salt-ds/core      | Patch |
| @salt-ds/data-grid | Patch |
| @salt-ds/countries | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/jpmorganchase/salt-ds/new/4794-status-adornment-alignment?filename=.changeset/blue-feet-drop.md&value=---%0A%22%40salt-ds%2Fcore%22%3A%20patch%0A%22%40salt-ds%2Flab%22%3A%20patch%0A---%0A%0AFix%20status%20adornment%20alignment%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-11T10:19:11Z
body: [vc]: #+r3oegTs9GbyO/2fFh8YgrtWSx/7Xt5qA3rU3y/W6cs=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzYWx0ZGVzaWduc3lzdGVtIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2ZlZC10ZWFtL3NhbHRkZXNpZ25zeXN0ZW0vQ05ES2VUaEZqWVZLRm1QaDdEenNKOUh4eW9vdSIsInByZXZpZXdVcmwiOiJzYWx0ZGVzaWduc3lzdGVtLWdpdC00Nzk0LXN0YXR1cy1hZG9ybm1lbnQtYWxpZ25tZW50LWZlZC10ZWFtLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoic2FsdGRlc2lnbnN5c3RlbS1naXQtNDc5NC1zdGF0dXMtYWRvcm5tZW50LWFsaWdubWVudC1mZWQtdGVhbS52ZXJjZWwuYXBwIn0sInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **saltdesignsystem** | ✅ Ready ([Inspect](https://vercel.com/fed-team/saltdesignsystem/CNDKeThFjYVKFmPh7DzsJ9Hxyoou)) | [Visit Preview](https://saltdesignsystem-git-4794-status-adornment-alignment-fed-team.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saltdesignsystem-git-4794-status-adornment-alignment-fed-team.vercel.app?via=pr-comment-feedback-link) | Mar 28, 2025 8:16am |


author_association: NONE
user: login: origami-z
created_at: 2025-03-19T16:36:05Z
body: @mark-tate @joshwooding can you review this please.. for MTK
author_association: CONTRIBUTOR