Skip to content

improvement(spacing): make row Stack separators self-orienting (CUI-38) - #1182

Draft
JeanMarcMilletScality wants to merge 1 commit into
development/1.0from
improvement/CUI-38-self-orienting-separator
Draft

improvement(spacing): make row Stack separators self-orienting (CUI-38)#1182
JeanMarcMilletScality wants to merge 1 commit into
development/1.0from
improvement/CUI-38-self-orienting-separator

Conversation

@JeanMarcMilletScality

@JeanMarcMilletScality JeanMarcMilletScality commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

TL;DR — A Stack's row separators now orient themselves from their flex parent instead of from the direction prop, so a consumer can collapse a row into a column in pure CSS (a container query) and the separators follow. Nothing renders differently today.

Context / Why

Stack picks its separator element from the direction prop, at render time. A CSS flex-direction flip — which is how container-query-driven responsive layouts work — leaves the separators pointing the wrong way. This blocks the ARTESCA overview page (clips its first KPI tile below ~875px, fixed by flipping a Stack in a container query) and would block identity-ui, zenko-ui and metalk8s the moment they flip one. CUI-38

🧩 Approach

Only the row separator needs to change. A consumer collapsing a row into a column keeps direction="horizontal" as their base and flips in CSS — the prop never changes, so the vertical branch is never involved in the responsive case. That makes the fix behaviour-preserving rather than a restyle.

The row separator becomes direction-agnostic by exploiting the fact that flex-basis resolves against the main axis while align-self resolves against the cross axis:

Before — orientation hard-coded into the element:

const HSeparator = styled.div`
  background: ${(props) => props.theme.border};
  width: 1px;                  // ← fixed cross-axis size: only ever a vertical rule
  align-self: stretch;
  flex-shrink: 0;
  margin: ${spacing.r12} 0px;
`;

After — orientation resolved by the parent's axis:

const SelfOrientingSeparator = styled.div`
  background: ${(props) => props.theme.border};
  flex: 0 0 1px;               // ← resolves against the MAIN axis
  align-self: stretch;         // ← resolves against the CROSS axis
  margin: ${spacing.r12} 0;
`;
Parent flex-direction flex-basis: 1px sets align-self: stretch sets Result
row width height 1px vertical rule, full stack height
column height width 1px horizontal rule, full stack width

flex: 0 0 1px is flex-grow: 0; flex-shrink: 0; flex-basis: 1px — the same fixed, non-shrinking 1px the old width + flex-shrink pair produced, just expressed against the main axis instead of the horizontal one. Measured in a real browser, with the old CSS simulated by overriding the new declarations back on the same DOM:

Banner-story separator old CSS new CSS
1 1.0 × 38.75 1.0 × 38.75 ✅ bit-identical
2 1.0 × 38.75 1.0 × 38.75 ✅ bit-identical

The vertical dash is deliberately left alone (VSeparatorDashSeparator, rename only). An element with an explicit cross-axis size can't self-orient, so making the vertical case work in CSS would have meant turning it into a full-width rule — restyling all 5 vertical production call sites, adding 1.5rem of separation, and needing design sign-off. Worse, in a page Form the result duplicates the header's own border-bottom: 1px solid theme.border (Form.component.tsx:102): same weight, same colour, near-same width, so a section separator becomes indistinguishable from the form title's rule. A fix shouldn't introduce a new issue — so the vertical branch stays exactly as it is, and the responsive case doesn't need it.

The direction flip was verified with no re-render — measured by mutating container.style.width and forcing reflow only; DOM node identity held and the separator count stayed at 2. Wide (700px): row, separators 1 × 38.75. Narrow (380px): column, separators 380 × 1.

📷 Screenshots

🔧 Usage

No existing Stack usage changes. What this enables is new — a Stack whose direction comes from CSS rather than the prop (from the story added in this PR):

const ResponsiveStack = styled(Stack)`
  @container (max-width: 500px) {
    && {
      flex-direction: column;
      align-items: stretch;
    }
  }
`;

<ResponsiveStack withSeparators gap="r24"></ResponsiveStack>

Two notes for consumers doing this:

  • The && is required — it doubles specificity to beat the flex-direction that Box emits from Stack's direction prop.
  • Override align-items as well as flex-direction. Stack still derives both from the prop; the separator itself is immune, since its align-self beats the parent's align-items.
  • Keep direction at its default (horizontal). Flipping a direction="vertical" Stack to a row in CSS is still unsupported — its dash can't self-orient — but that's the pre-existing behaviour, not something this PR changes.

🔍 Review focus

  • 🟡 Moderatesrc/lib/spacing.tsx › SelfOrientingSeparator — the flex: 0 0 1px + align-self: stretch pair is the mechanism, and the claim that it's a no-op in a row rests on it. Replacing either with an explicit width/height silently reintroduces the original bug, with no test to catch it (nothing asserts separator DOM).
  • Minorsrc/lib/spacing.tsx › Separator — the type prop and the two-branch wrapper are retained on purpose, so the vertical dash is untouched. HSeparator/VSeparator are renamed to SelfOrientingSeparator/DashSeparator — the old names were inverted (HSeparator was used for direction="horizontal" but drew a vertical rule). Both are file-local; Separator was never exported, so no public API changes.

🧪 How to test

  1. npm run storybookComponents/Styling/Spacing Utils › Separators follow a CSS direction flip.
  2. Drag the container's bottom-right resize handle below 500px. The stack flips to a column and the separators become full-width horizontal rules; drag back above 500px and they return to vertical rules. Nothing re-renders — only CSS changes.
  3. Regression check on Stack Story: both the Banner example (horizontal) and the Vertical divided example must be pixel-identical to development/1.0.
  4. Same for the two in-repo vertical production sites under Templates/Form (page-form, tab-form, form-with-accordion) — the section separators there should be unchanged short dashes, still clearly subordinate to the form title's rule.

🚧 Follow-up

  • After merge + npm release, bump @scality/core-ui in artesca/@artesca/ui/package.json directly (@artesca/ui is built in-repo — not integrate-components, not solutions/*/variable.mk). That unblocks the ARTESCA overview-page and certificates responsive tickets.
  • A vertical Stack still can't be flipped to a row in CSS. No consumer needs it today; it would require the full-width-rule restyle plus a design decision on the page-Form rule collision described above. Not yet ticketed.
  • Wrap is deliberately untouched — it does not wrap despite its name, deferred pending a usage audit. Not yet ticketed.

🔗 References

  • CUI-38 — Unify Stack separators into one self-orienting element so direction can change in CSS. Note the ticket describes collapsing both separators into one element; this PR deliberately narrows that to the row separator only, to avoid restyling existing vertical call sites.
  • CUI-39 / bugfix(icon): keep IconWrapper circular under flex pressure (CUI-39) #1183 — sibling core-ui fix from the same ARTESCA responsive handoff (IconWrapper deforming into an ellipse), kept as its own PR so either can merge and release independently.
What changed

src/lib/spacing.tsxHSeparator becomes SelfOrientingSeparator, trading width: 1px + flex-shrink: 0 for flex: 0 0 1px (equivalent in a row, correct in a column). VSeparatorDashSeparator is a rename with no CSS change. The Separator({ type }) wrapper and Stack's type={direction} stay. The dropped &nbsp; in the row branch is inert — the element's height comes from align-self: stretch, not from content, which the bit-identical measurement above confirms.

stories/spacing.stories.tsx — adds ContainerQueryDirectionFlip, covering the case this change exists to enable and which nothing covered before: a container-type: inline-size resizable box wrapping a styled(Stack) that flips direction in a @container query.

Deliberately not in this PR: Wrap (out of scope per CUI-38), the full-width-rule restyle of vertical separators, and any Form change — with the dash retained there is no regression to fix there.

tsc --noEmit clean; 317 tests / 35 suites green on the files related to spacing.tsx.

@bert-e

bert-e commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request.
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

Stack picked its separator element from the direction prop at render time,
injecting HSeparator (a vertical rule) or VSeparator (a 24px horizontal dash).
A CSS flex-direction flip — how container-query-driven responsive layouts work —
left the separators pointing the wrong way, blocking responsive work in ARTESCA
and every other repo consuming Stack.

Only the row separator needs to change: a consumer collapsing a row into a
column keeps direction="horizontal" as the base and flips in CSS, so the prop
never changes. SelfOrientingSeparator replaces HSeparator using flex-basis for
the main axis and align-self for the cross axis, which renders identically to
the fixed width: 1px while the Stack stays a row and becomes a full-width rule
once CSS flips it to a column.

VSeparator (renamed DashSeparator) is kept as-is, so no existing vertical call
site is restyled. Making it a full-width rule would have duplicated the page
Form header's own 1px border, leaving no distinction between the form title and
a section separator.

No public API change: Separator was never exported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JeanMarcMilletScality
JeanMarcMilletScality force-pushed the improvement/CUI-38-self-orienting-separator branch from 2dfb90d to f5e87ef Compare August 12, 2026 16:41
@JeanMarcMilletScality JeanMarcMilletScality changed the title improvement(spacing): make Stack separators self-orienting (CUI-38) improvement(spacing): make row Stack separators self-orienting (CUI-38) Aug 12, 2026
@JeanMarcMilletScality
JeanMarcMilletScality marked this pull request as draft August 13, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants