diff --git a/.changeset/icon-baseline-alignment.md b/.changeset/icon-baseline-alignment.md new file mode 100644 index 0000000000..5cdcf444b9 --- /dev/null +++ b/.changeset/icon-baseline-alignment.md @@ -0,0 +1,5 @@ +--- +"@cloudoperators/juno-ui-components": patch +--- + +fix(ui): collapse Icon line-box so icons align reliably inside flex containers, with follow-up fixes in PopupMenu, ComboBox, and Select diff --git a/packages/ui-components/src/components/ComboBox/ComboBox.component.tsx b/packages/ui-components/src/components/ComboBox/ComboBox.component.tsx index c005e22a34..5dacc809b2 100644 --- a/packages/ui-components/src/components/ComboBox/ComboBox.component.tsx +++ b/packages/ui-components/src/components/ComboBox/ComboBox.component.tsx @@ -102,6 +102,10 @@ const buttonStyles = ` jn:pr-2 jn:h-textinput jn:w-8 + jn:inline-flex + jn:items-center + jn:justify-end + jn:leading-none jn:border-l-0 jn:border-y jn:border-r @@ -144,14 +148,19 @@ const menuStyles = ` const iconContainerStyles = ` jn:absolute - jn:top-[.4rem] + jn:top-1/2 jn:right-8 + jn:inline-flex + jn:leading-none + jn:translate-y-[-50%] ` const centeredIconStyles = ` jn:absolute jn:top-1/2 jn:left-1/2 + jn:inline-flex + jn:leading-none jn:translate-y-[-50%] jn:translate-x-[-0.75rem] ` diff --git a/packages/ui-components/src/components/Icon/Icon.component.tsx b/packages/ui-components/src/components/Icon/Icon.component.tsx index 6cc9d7f06a..4eef4f9df6 100644 --- a/packages/ui-components/src/components/Icon/Icon.component.tsx +++ b/packages/ui-components/src/components/Icon/Icon.component.tsx @@ -71,8 +71,10 @@ Generic Icon component. // hover style needs to be revisited. only works if no icon color was passed const anchorIconStyles = ` jn:text-current + jn:inline-flex + jn:leading-none jn:hover:text-theme-high - jn:focus:outline-hidden + jn:focus:outline-hidden jn:focus-visible:ring-2 jn:focus-visible:ring-theme-focus jn:focus-visible:ring-offset-1 @@ -83,8 +85,10 @@ const anchorIconStyles = ` // hover style needs to be revisited. only works if no icon color was passed const buttonIconStyles = ` + jn:inline-flex + jn:leading-none jn:hover:text-theme-high - jn:focus:outline-hidden + jn:focus:outline-hidden jn:focus-visible:ring-2 jn:focus-visible:ring-theme-focus jn:focus-visible:ring-offset-1 @@ -998,7 +1002,7 @@ export const Icon = forwardRef /* render an if href was passed, otherwise render button if onClick was passes, otherwise render plain icon: */ /* if plain icon, add ref to the icon. In the other cases the ref goes on the anchor or button */ - return href ? anchor : onClick ? button : {icn} + return href ? anchor : onClick ? button : {icn} }) export interface IconProps extends Omit | HTMLProps, "size"> { diff --git a/packages/ui-components/src/components/PageHeader/PageHeader.stories.tsx b/packages/ui-components/src/components/PageHeader/PageHeader.stories.tsx index dda5a50071..8f139fbfec 100644 --- a/packages/ui-components/src/components/PageHeader/PageHeader.stories.tsx +++ b/packages/ui-components/src/components/PageHeader/PageHeader.stories.tsx @@ -13,6 +13,8 @@ import CustomLogoPortraitPNG from "./custom-logo-placeholders/custom-logo-portra import CustomLogoSquarePNG from "./custom-logo-placeholders/custom-logo-square.png" import { PageHeader } from "./index" import { Button } from "../Button/" +import { ThemeToggle } from "../ThemeToggle/" +import { PopupMenu, PopupMenuOptions, PopupMenuItem } from "../PopupMenu/" type Story = StoryObj @@ -187,3 +189,29 @@ export const WithWrappedLogo: Story = { ), }, } + +export const WithThemeToggleAndUserMenu: Story = { + parameters: { + docs: { + description: { + story: + "Visual test for icon alignment: a `ThemeToggle` next to a `PopupMenu` (default icon toggle using `accountCircle`) inside a flex container with `align-items: center`. Both icons should appear vertically centered with no extra space below the user-menu icon.", + }, + }, + }, + args: { + applicationName: "My Awesome App", + children: ( +
+ + + + + + + + +
+ ), + }, +} diff --git a/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx b/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx index 4a06e339be..f1c44f32a9 100644 --- a/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx +++ b/packages/ui-components/src/components/PopupMenu/PopupMenu.component.tsx @@ -25,6 +25,15 @@ import { PortalProvider } from "../PortalProvider/" // ----- Styles ----- +// Base styles applied to every toggle (default and custom). +// inline-flex + leading-none collapse the button's line box to its content, +// so an Icon child sits without descender space below it. +const baseToggleStyles = ` + jn:inline-flex + jn:items-center + jn:leading-none +` + const defaultToggleStyles = ` jn:hover:text-theme-accent jn:active:text-theme-accent @@ -107,7 +116,7 @@ const sectionTitleStyles = ` ` const sectionSeparatorStyles = ` - jn:h- + jn:h-px jn:bg-theme-background-lvl-3 ` @@ -116,6 +125,13 @@ const floatingReferenceWrapperStyles = ` jn:inline-flex ` +// The outer wrapper must also be a flex container (rather than a default block element), +// otherwise its inline-flex child sits in an anonymous line box and inherits line-height, +// reintroducing descender space below the toggle. +const popupMenuWrapperStyles = ` + jn:inline-flex +` + // ----- Interfaces ----- export interface PopupMenuContextType { @@ -256,7 +272,7 @@ const PopupMenu = ({ const menu = childrenArray.find((child) => isValidElement(child) && child.type === PopupMenuOptions) return ( - + {/* Update our open state when the open render prop from headless ui menu changes, so we can run the handlers when our tracking state changes: */} {({ open, close }) => { // Set our tracking open state outside of rendering cycle. Trying to set the state during render will cause an error: @@ -322,7 +338,7 @@ export const PopupMenuToggle = ({ }: PopupMenuToggleProps): ReactNode => ( diff --git a/packages/ui-components/src/components/Select/Select.component.tsx b/packages/ui-components/src/components/Select/Select.component.tsx index 270ca91299..ac023e217e 100644 --- a/packages/ui-components/src/components/Select/Select.component.tsx +++ b/packages/ui-components/src/components/Select/Select.component.tsx @@ -60,6 +60,8 @@ const centeredIconStyles = ` jn:absolute jn:top-1/2 jn:left-1/2 + jn:inline-flex + jn:leading-none jn:translate-y-[-50%] jn:translate-x-[-0.75rem] ` @@ -395,10 +397,10 @@ export const Select = ({ > {getDisplayValue(value)} - + {isValid ? : ""} {isInvalid ? : ""} - +