diff --git a/ts/axo/_internal/css.dom.tsx b/ts/axo/_internal/css.dom.tsx new file mode 100644 index 0000000000..00310438d5 --- /dev/null +++ b/ts/axo/_internal/css.dom.tsx @@ -0,0 +1,37 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { TailwindStyles } from '../tw.dom.tsx'; + +/** @internal */ +export function concatClassNames( + classNames: ReadonlyArray +): TailwindStyles { + const { length } = classNames; + + let result = ''; + let first = true; + + for (let index = 0; index < length; index += 1) { + const className = classNames[index]; + if (typeof className === 'string') { + if (first) { + first = false; + } else { + result += ' '; + } + result += className; + } + } + + return result as TailwindStyles; +} + +/** @internal */ +export function css( + ...classnames: Array< + TailwindStyles | `axo-${string}` | boolean | null | undefined + > +): TailwindStyles { + return concatClassNames(classnames); +} diff --git a/ts/axo/items/AxoItem.dom.stories.tsx b/ts/axo/items/AxoItem.dom.stories.tsx index 26451de40a..866a22cb53 100644 --- a/ts/axo/items/AxoItem.dom.stories.tsx +++ b/ts/axo/items/AxoItem.dom.stories.tsx @@ -639,7 +639,7 @@ export function StressTests(): ReactNode { - These should all break into a stacked layout. + Long labels should not break into a stacked layout. diff --git a/ts/axo/items/_AxoBaseItem.css b/ts/axo/items/_AxoBaseItem.css index 2a0c7ca587..f5d76754be 100644 --- a/ts/axo/items/_AxoBaseItem.css +++ b/ts/axo/items/_AxoBaseItem.css @@ -4,7 +4,7 @@ */ @utility axo-item-centering-to-max-height { - @apply flex h-full min-h-fit items-center; + @apply flex h-[stretch] min-h-fit items-center; /* --axo-leading-body-medium + --axo-leading-body-small + (description margin top) */ max-height: 38px; } @@ -50,6 +50,15 @@ @apply min-w-0; /* Leave room for focus rings */ @apply -m-1; + + /* Provider scroll state container */ + @apply @container-[scroll-state] overflow-x-hidden; + } + + :where(.axo-item-content) .axo-item-content-force-scroll-state { + /* We need this fix or the item will get stuck in a layout cycle */ + /* Force scroll-state to stay stuck as scrollable once it becomes scrollable */ + @apply relative container-scrollable:w-[calc(100%+1px)]; } :where(.axo-item-content) .axo-item-content-inner { @@ -64,6 +73,7 @@ } :where(.axo-item-body) .axo-item-label { + @apply min-w-50; @apply type-body-medium text-primary; } @@ -72,9 +82,9 @@ } :where(.axo-item-body) .axo-item-description { + @apply min-w-50; @apply mt-0.5; @apply type-body-small text-secondary forced-colors:text-[GrayText]; - @apply min-w-[min(max-content,120px)]; @apply wrap-break-word [word-break:auto-phrase]; } @@ -102,7 +112,7 @@ .axo-item-label { grid-area: axo-item-label; @apply self-center; - @apply text-nowrap; + @apply contain-inline-size contain-layout; } .axo-item-value { @@ -125,11 +135,6 @@ /* When overflowing container */ @container scroll-state(scrollable: inline) { - .axo-item-content-inner { - width: calc(100% + 1px); - padding-inline-end: 1px; - } - .axo-item-accessory { @apply mt-0.5; @apply flex-col items-stretch; diff --git a/ts/axo/items/_AxoBaseItem.dom.tsx b/ts/axo/items/_AxoBaseItem.dom.tsx index 6dd8c94f4b..f9d419469b 100644 --- a/ts/axo/items/_AxoBaseItem.dom.tsx +++ b/ts/axo/items/_AxoBaseItem.dom.tsx @@ -6,6 +6,7 @@ import { createStrictContext, useStrictContext, } from '../_internal/StrictContext.dom.tsx'; +import { css } from '../_internal/css.dom.tsx'; import { tw } from '../tw.dom.tsx'; import { AriaClickable } from '../AriaClickable.dom.tsx'; import { forwardExtraPropsForRadix } from '../_internal/props.dom.tsx'; @@ -15,21 +16,6 @@ import { AxoButton } from '../AxoButton.dom.tsx'; import { AxoCheckbox } from '../AxoCheckbox.dom.tsx'; import { AxoAvatar } from '../AxoAvatar.dom.tsx'; import { variants } from '../_internal/variants.dom.tsx'; -import { FlexWrapDetector } from '../_internal/FlexWrapDetector.dom.tsx'; - -const AXO_ITEM_GROUP_CLASS = 'axo-item-group'; -const AXO_ITEM_ROOT_CLASS = 'axo-item-root'; -const AXO_ITEM_ROOT_INNER_CLASS = 'axo-item-root-inner'; -const AXO_ITEM_LEADING_CLASS = 'axo-item-leading'; -const AXO_ITEM_CONTENT_CLASS = 'axo-item-content'; -const AXO_ITEM_CONTENT_INNER_CLASS = 'axo-item-content-inner'; -const AXO_ITEM_BODY_CLASS = 'axo-item-body'; -const AXO_ITEM_LABEL_CLASS = 'axo-item-label'; -const AXO_ITEM_ACCESSORY_CLASS = 'axo-item-accessory'; -const AXO_ITEM_VALUE_CLASS = 'axo-item-value'; -const AXO_ITEM_DESCRIPTION_CLASS = 'axo-item-description'; -const AXO_ITEM_TRAILING_CLASS = 'axo-item-trailing'; -const AXO_ITEM_ARROW_CLASS = 'axo-item-arrow'; /** * @example Anatomy @@ -92,7 +78,7 @@ export namespace AxoBaseItem {
{children} @@ -161,14 +147,14 @@ export namespace AxoBaseItem {
{children} @@ -191,7 +177,7 @@ export namespace AxoBaseItem { }>; export const Leading: FC = memo(props => { - return
{props.children}
; + return
{props.children}
; }); Leading.displayName = 'AxoBaseItem.Leading'; @@ -281,10 +267,9 @@ export namespace AxoBaseItem { export const Content: FC = memo(props => { return ( -
- -
{props.children}
-
+
+
{props.children}
+
); }); @@ -301,7 +286,7 @@ export namespace AxoBaseItem { }>; export const Body: FC = memo(props => { - return
{props.children}
; + return
{props.children}
; }); Body.displayName = 'AxoBaseItem.Body'; @@ -323,9 +308,9 @@ export namespace AxoBaseItem { return (
@@ -383,10 +368,10 @@ export namespace AxoBaseItem { return (
@@ -436,7 +421,7 @@ export namespace AxoBaseItem { }>; export const Accessory: FC = memo(props => { - return
{props.children}
; + return
{props.children}
; }); Accessory.displayName = 'AxoBaseItem.Accessory'; @@ -523,7 +508,7 @@ export namespace AxoBaseItem { }>; export const Trailing: FC = memo(props => { - return
{props.children}
; + return
{props.children}
; }); Trailing.displayName = 'AxoBaseItem.Trailing'; @@ -552,9 +537,9 @@ export namespace AxoBaseItem { const { disabled } = useStrictContext(RootContext); return (
diff --git a/ts/axo/tw.dom.tsx b/ts/axo/tw.dom.tsx index 8b4c72fe4b..9950f24ee7 100644 --- a/ts/axo/tw.dom.tsx +++ b/ts/axo/tw.dom.tsx @@ -1,6 +1,8 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { concatClassNames } from './_internal/css.dom.tsx'; + /** Opaque type for styles returned by tw() */ export type TailwindStyles = string & { __Styles: never }; @@ -21,22 +23,5 @@ export function tw( TailwindStyles | string | boolean | null | undefined > ): TailwindStyles { - const { length } = classNames; - - let result = ''; - let first = true; - - for (let index = 0; index < length; index += 1) { - const className = classNames[index]; - if (typeof className === 'string') { - if (first) { - first = false; - } else { - result += ' '; - } - result += className; - } - } - - return result as TailwindStyles; + return concatClassNames(classNames); }