mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-09-29 00:14:16 +01:00
Allow AxoItem labels to wrap before switching to stacked layout
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
co-authored by
Jamie
parent
1aeebfbbc6
commit
059eb85a4c
@@ -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<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;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function css(
|
||||
...classnames: Array<
|
||||
TailwindStyles | `axo-${string}` | boolean | null | undefined
|
||||
>
|
||||
): TailwindStyles {
|
||||
return concatClassNames(classnames);
|
||||
}
|
||||
@@ -639,7 +639,7 @@ export function StressTests(): ReactNode {
|
||||
|
||||
<Story.Legend label="Long label">
|
||||
<Story.Callout>
|
||||
These should all break into a stacked layout.
|
||||
Long labels should <em>not</em> break into a stacked layout.
|
||||
</Story.Callout>
|
||||
|
||||
<StressTest label={LONG_LABEL} />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
<GroupContext value={context}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={AXO_ITEM_GROUP_CLASS}
|
||||
className="axo-item-group"
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
{children}
|
||||
@@ -161,14 +147,14 @@ export namespace AxoBaseItem {
|
||||
<RootContext value={context}>
|
||||
<AriaClickable.Root asChild>
|
||||
<div
|
||||
className={tw(AXO_ITEM_ROOT_CLASS, 'group')}
|
||||
className={css('axo-item-root', tw('group'))}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
<div
|
||||
className={tw(
|
||||
AXO_ITEM_ROOT_INNER_CLASS,
|
||||
className={css(
|
||||
'axo-item-root-inner',
|
||||
RootSpacing.get(groupContext.spacing),
|
||||
disabled && 'text-disabled'
|
||||
disabled && tw('text-disabled')
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -191,7 +177,7 @@ export namespace AxoBaseItem {
|
||||
}>;
|
||||
|
||||
export const Leading: FC<LeadingProps> = memo(props => {
|
||||
return <div className={AXO_ITEM_LEADING_CLASS}>{props.children}</div>;
|
||||
return <div className="axo-item-leading">{props.children}</div>;
|
||||
});
|
||||
|
||||
Leading.displayName = 'AxoBaseItem.Leading';
|
||||
@@ -281,10 +267,9 @@ export namespace AxoBaseItem {
|
||||
|
||||
export const Content: FC<ContentProps> = memo(props => {
|
||||
return (
|
||||
<div className={AXO_ITEM_CONTENT_CLASS}>
|
||||
<FlexWrapDetector>
|
||||
<div className={AXO_ITEM_CONTENT_INNER_CLASS}>{props.children}</div>
|
||||
</FlexWrapDetector>
|
||||
<div className="axo-item-content">
|
||||
<div className="axo-item-content-inner">{props.children}</div>
|
||||
<div className="axo-item-content-force-scroll-state" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -301,7 +286,7 @@ export namespace AxoBaseItem {
|
||||
}>;
|
||||
|
||||
export const Body: FC<BodyProps> = memo(props => {
|
||||
return <div className={AXO_ITEM_BODY_CLASS}>{props.children}</div>;
|
||||
return <div className="axo-item-body">{props.children}</div>;
|
||||
});
|
||||
|
||||
Body.displayName = 'AxoBaseItem.Body';
|
||||
@@ -323,9 +308,9 @@ export namespace AxoBaseItem {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={tw(
|
||||
AXO_ITEM_LABEL_CLASS,
|
||||
truncate && 'truncate',
|
||||
className={css(
|
||||
'axo-item-label',
|
||||
truncate && tw('truncate'),
|
||||
disabled ? DisabledVariants.get(variant) : Variants.get(variant)
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
@@ -353,9 +338,9 @@ export namespace AxoBaseItem {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={tw(
|
||||
AXO_ITEM_VALUE_CLASS,
|
||||
disabled && 'text-disabled forced-colors:text-[GrayText]'
|
||||
className={css(
|
||||
'axo-item-value',
|
||||
disabled && tw('text-disabled forced-colors:text-[GrayText]')
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
@@ -383,10 +368,10 @@ export namespace AxoBaseItem {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={tw(
|
||||
AXO_ITEM_DESCRIPTION_CLASS,
|
||||
truncate && 'truncate',
|
||||
disabled && 'text-disabled'
|
||||
className={css(
|
||||
'axo-item-description',
|
||||
truncate && tw('truncate'),
|
||||
disabled && tw('text-disabled')
|
||||
)}
|
||||
{...forwardExtraPropsForRadix(rest)}
|
||||
>
|
||||
@@ -436,7 +421,7 @@ export namespace AxoBaseItem {
|
||||
}>;
|
||||
|
||||
export const Accessory: FC<AccessoryProps> = memo(props => {
|
||||
return <div className={AXO_ITEM_ACCESSORY_CLASS}>{props.children}</div>;
|
||||
return <div className="axo-item-accessory">{props.children}</div>;
|
||||
});
|
||||
|
||||
Accessory.displayName = 'AxoBaseItem.Accessory';
|
||||
@@ -523,7 +508,7 @@ export namespace AxoBaseItem {
|
||||
}>;
|
||||
|
||||
export const Trailing: FC<TrailingProps> = memo(props => {
|
||||
return <div className={AXO_ITEM_TRAILING_CLASS}>{props.children}</div>;
|
||||
return <div className="axo-item-trailing">{props.children}</div>;
|
||||
});
|
||||
|
||||
Trailing.displayName = 'AxoBaseItem.Trailing';
|
||||
@@ -552,9 +537,9 @@ export namespace AxoBaseItem {
|
||||
const { disabled } = useStrictContext(RootContext);
|
||||
return (
|
||||
<div
|
||||
className={tw(
|
||||
AXO_ITEM_ARROW_CLASS,
|
||||
disabled && 'text-disabled forced-colors:text-[GrayText]'
|
||||
className={css(
|
||||
'axo-item-arrow',
|
||||
disabled && tw('text-disabled forced-colors:text-[GrayText]')
|
||||
)}
|
||||
>
|
||||
<AxoSymbol.InlineGlyph label={null} symbol={ArrowKinds.get(kind)} />
|
||||
|
||||
+3
-18
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user