Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/banner-single-line-toggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] Banner: a title-only collapsible banner now centers its header, as a title-only banner with any other control already does. `isSingleLine` counted `endContent` and the dismiss button but not the collapse toggle, so a banner whose only control was the toggle kept `align-items: flex-start` — leaving its icon and title 4px above the 28px toggle they sit beside, while the same banner with a dismiss button centered correctly.

@freddymeta
9 changes: 9 additions & 0 deletions .changeset/banner-theme-targets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@astryxdesign/core': patch
---

[feat] Banner: the two painted elements inside the header that a theme cannot reach now carry stable theme targets — `astryx-banner-description` (the supporting line) and `astryx-banner-actions` (the end-aligned actions row). Only the header, the status icon and the content panel were themeable before, so a theme restyling the description, the space between it and the title, or the actions row's edge compensation had to reach in with structural selectors like `.astryx-banner > div:nth-child(2) > div:nth-child(2)`. Purely additive: no existing class, data attribute, or style changes.

The title, the two controls and the header's text column are deliberately not targets. The column paints nothing (`display: flex; flex-direction: column; gap: 0`) and the space it owns is expressible on `banner-description`; the title and the controls already render the way the consuming theme wants them.

@freddymeta
8 changes: 8 additions & 0 deletions packages/core/src/Banner/Banner.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export const docs = {
targets: [
{className: 'astryx-banner', visualProps: ['container', 'status']},
{className: 'astryx-banner-icon', visualProps: ['status']},
{className: 'astryx-banner-description'},
{className: 'astryx-banner-actions'},
{className: 'astryx-banner-content', visualProps: ['container', 'status']},
],
vars: [
Expand Down Expand Up @@ -192,6 +194,12 @@ export const docsZh = {
'status',
],
},
{
className: 'astryx-banner-description',
},
{
className: 'astryx-banner-actions',
},
{
className: 'astryx-banner-content',
visualProps: [
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/Banner/Banner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -650,4 +650,44 @@ describe('Banner', () => {
expect(getComputedStyle(textColumn).flexBasis).not.toBe('8rem');
});
});
describe('single-line centering', () => {
const headerOf = (ui: React.ReactElement) => {
const {container} = render(ui);
return container.firstElementChild!.firstElementChild!;
};

it('centers a title-only banner that has a dismiss button', () => {
const header = headerOf(
<Banner status="info" title="Deploy finished" isDismissable />,
);
expect(getComputedStyle(header).alignItems).toBe('center');
});

it('centers a title-only banner whose only control is the collapse toggle', () => {
const header = headerOf(
<Banner status="info" title="Deploy finished">
<p>Details</p>
</Banner>,
);
expect(getComputedStyle(header).alignItems).toBe('center');
});

it('keeps a described banner top-aligned, toggle or not', () => {
const header = headerOf(
<Banner status="info" title="Deploy finished" description="Two lines">
<p>Details</p>
</Banner>,
);
expect(getComputedStyle(header).alignItems).toBe('flex-start');
});

it('keeps a banner with no controls at all top-aligned', () => {
const header = headerOf(
<Banner status="info" title="Deploy finished" collapsible={false}>
<p>Details</p>
</Banner>,
);
expect(getComputedStyle(header).alignItems).toBe('flex-start');
});
});
});
29 changes: 23 additions & 6 deletions packages/core/src/Banner/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
* - Status icon (themeProps 'banner-icon'): the target rides on the default
* <Icon> itself — the element that paints — so 'status:X' overrides reach
* the glyph (#4166); for a custom `icon` node it stays on the layout wrapper
* - Description (themeProps 'banner-description'): the supporting line owns its
* own colour and type, and the space between it and the title
* - End area (themeProps 'banner-actions'): the actions row, including the edge
* compensation — a theme whose banner grows to its buttons rather than
* letting them overhang the header padding overrides it here
* - No left border accent — color is expressed through the full header background
* - Each visual area owns its own border-radius (no overflow:clip on the container)
* - Children are collapsible by default: a toggle appears in the header end
Expand Down Expand Up @@ -534,8 +539,11 @@ export function Banner({
// Show the end area if there are actions, dismiss, or a collapsible toggle
const showEndArea = isRenderable(endContent) || isDismissable || hasToggle;
// Center items vertically when there's only a title (no description)
// and the banner has action buttons
const hasActions = isRenderable(endContent) || isDismissable;
// and the banner has action buttons. The collapse toggle counts: it is the
// same 28px control as a dismiss button and shares the row with it, so a
// collapsible title-only banner is exactly the case this centers — leaving
// it out left the icon and title hanging above the toggle they sit beside.
const hasActions = isRenderable(endContent) || isDismissable || hasToggle;
const isSingleLine = !isRenderable(description) && hasActions;

// Non-collapsible children are always shown; collapsible ones follow the
Expand Down Expand Up @@ -612,14 +620,23 @@ export function Banner({
)}>
<div {...stylex.props(styles.title)}>{title}</div>
{isRenderable(description) && (
<div {...stylex.props(styles.description)}>{description}</div>
<div
{...mergeProps(
themeProps('banner-description'),
stylex.props(styles.description),
)}>
{description}
</div>
)}
</div>
{showEndArea && (
<div
{...stylex.props(
styles.endArea,
edgeCompSlot.inset(spacingVars['--spacing-2']),
{...mergeProps(
themeProps('banner-actions'),
stylex.props(
styles.endArea,
edgeCompSlot.inset(spacingVars['--spacing-2']),
),
)}>
{endContent}
{hasToggle && (
Expand Down
Loading