Skip to content

feat(SidenavBar): full specs implementation - #1673

Open
brtbrt wants to merge 25 commits into
masterfrom
mbertamini/1592-sidenav-bar
Open

feat(SidenavBar): full specs implementation#1673
brtbrt wants to merge 25 commits into
masterfrom
mbertamini/1592-sidenav-bar

Conversation

@brtbrt

@brtbrt brtbrt commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary - AI;DR

This PR adds the SidenavBar component and the desktop, mobile, and double-panel behaviours from the Figma spec. Read the sections below in order. They tell you where to look and what still blocks the merge.

Bulky PR

I'm considering opening a PR to simply import new icons as well as the new skins (along with the new tokens we need).
This way, the PR would look less cluttered and bulky. WDYT? Let me know.

Specs

The markdown spec and the token branch live on the aweell-generate-figma-specs branch of mistica-design. That branch is not final. See the blockers below.

Review guide

1. Shared mobile module (start here)
The mobile sidenav must look and behave like the mobile nav bar. So this PR extracts the common parts of navigation-bar into a shared module. Both components now consume it.

  • src/navigation-shared.tsx, src/navigation-shared.css.ts, src/navigation-types.ts, src/mobile-navigation-menu.tsx — the new shared code.
  • src/navigation-bar.tsx and src/navigation-bar.css.ts — reduced, because they delegate to the shared module. Check that the nav bar still behaves the same.

2. Screaming architecture for src/sidenav-bar/
Everything for this component lives under one directory: implementation, styles, context, mobile, motion, keyboard, tests, stories, and snippets. The intent is faster work for a person and for an AI agent, because one directory holds the full feature. An agent reads one tree instead of searching the repo, so it spends fewer tokens.

  • This layout needed small config edits. See jest.unit.config.js and .eslintrc.yml.

3. Blockers — you must not merge before we fix these // TODO WIP

  • src/text-tokens.tsx — the es/de/pt strings are drafts. They need official localization sign-off. Only en is final.
  • src/sidenav-bar/sidenav-bar.css.ts:89 — the media variant hover and pressed tokens are missing. They come from the aweell-generate-figma-specs branch of mistica-design that is not final yet.
  • src/sidenav-bar/sidenav-motion.tsx:17 — an open question about whether to promote the motion hook to src/hooks.

4. Repo harness for specs and AI (support changes, not the feature)
This PR also improves how we work with specs and AI agents in this repo.

  • agents/skills/ plus the tool-specific symlinks — the read-component-specs and run-in-browser skills.
  • scripts/browse-story.js and scripts/open-story.js — drive a story in the CI Chromium Useful when it comes to measure things or check if we're complying spacing-mesasures etc, trying out things etc!

5. IntelliJ IDEA project files
This PR adds IntelliJ project files, the same way we already ship Visual Studio Code files. They make the repo easy to open in IntelliJ.

Ref: 1592

Pre-merge checklist

You must not merge before you address every item below.

  • Resolve the pending Figma comments.
  • Import from mistica-design with all the missing tokens.
  • Clarify and dismiss the other // TODO WIP markers in the code.

@brtbrt brtbrt added the AI AI Generated label Jul 29, 2026
@brtbrt
brtbrt requested a review from a team July 29, 2026 17:02
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Size stats

master this branch diff
Total JS 15.9 MB 16 MB +82.3 kB
JS without icons 1.96 MB 2.03 MB +73.5 kB
Lib overhead 99.1 kB 99.1 kB 0 B
Lib overhead (gzip) 21.4 kB 21.4 kB 0 B

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploy preview for mistica-web ready!

Project:mistica-web
Status: ✅  Deploy successful!
Preview URL:https://mistica-ceiezv3p9-flows-projects-65bb050e.vercel.app
Latest Commit:32a748b

Deployed with vercel-action

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Accessibility report
✔️ No issues found

ℹ️ You can run this locally by executing yarn audit-accessibility.

@brtbrt brtbrt changed the title feat(SidenavBar): add first draft of the component feat(SidenavBar): full specs implementation Aug 22, 2026
@brtbrt
brtbrt marked this pull request as ready for review August 24, 2026 18:00
Copilot AI lite review requested due to automatic review settings August 24, 2026 18:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the new SidenavBar component (plus SidenavLayout) and supporting infrastructure (shared navigation modules, stories/tests, generated tokens tooling) to implement the sidenav specs across desktop/mobile/double-panel variants.

Changes:

  • Introduces src/sidenav-bar/ (implementation, layout, motion hook, stories) and exports it from the package entrypoint.
  • Adds new i18n text tokens for SidenavBar accessibility labels and updates some acceptance/unit test expectations.
  • Improves design-token generation outputs (banners + stable prettier config resolution) and updates storybook/jest/tooling config to support the new “screaming architecture” layout.

Reviewed changes

Copilot reviewed 33 out of 2332 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/text-tokens.tsx Adds new SidenavBar text tokens (expand/collapse/submenu/landmark).
src/skins/vivo.tsx Generated skin update + adds generated-file banner.
src/skins/vivo-evolution.tsx Generated skin update (text presets/padding) + generated-file banner.
src/skins/types/colors.tsx Adds generated-file banner for generated type.
src/skins/telefonica.tsx Generated skin update + adds generated-file banner.
src/skins/o2.tsx Generated skin update + adds generated-file banner.
src/skins/movistar.tsx Generated skin update + adds generated-file banner.
src/skins/esimflag.tsx Generated skin update + adds generated-file banner.
src/skins/blau.tsx Generated skin update + adds generated-file banner.
src/sidenav-bar/stories/bar/sidenav-story-page.tsx Adds a story wrapper page using SidenavLayout.
src/sidenav-bar/stories/bar/sidenav-bar-right-slots-story.tsx Adds story showcasing rightSlot usage.
src/sidenav-bar/stories/bar/sidenav-bar-nested-story.tsx Adds story showcasing nested items.
src/sidenav-bar/sidenav-section.tsx Adds SidenavSection component.
src/sidenav-bar/sidenav-motion.tsx Adds reduced-motion hook used by SidenavBar JS-driven timing.
src/sidenav-bar/sidenav-layout.tsx Adds SidenavLayout component.
src/sidenav-bar/sidenav-layout.css.ts Adds layout styles (sticky rail + responsive stacking).
src/sidenav-bar/index.tsx Adds SidenavBar public exports from the sidenav-bar module.
src/portal.tsx Updates portal doc link to react.dev reference.
src/navigation-types.ts Adds shared navigation interactive prop types (href/to/onPress unions).
src/index.tsx Re-exports SidenavBar/SidenavLayout (and types) from the package entrypoint.
src/generated/mistica-icons/icon-text-size-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-terminal-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-terminal-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-terminal-console-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-subtract-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-stop-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-stop-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-stop-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-software-window-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-forward-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-forward-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-forward-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-back-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-back-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-skip-back-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-robot-vacuum-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-ring-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-ring-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-ring-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-recording-circle-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-low-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-low-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-low-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-highest-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-high-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-high-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-rank-high-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-panel-expand-regular.tsx Adds new generated panel-expand icon.
src/generated/mistica-icons/icon-panel-expand-light.tsx Adds new generated panel-expand icon.
src/generated/mistica-icons/icon-panel-expand-filled.tsx Adds new generated panel-expand icon.
src/generated/mistica-icons/icon-panel-collapse-regular.tsx Adds new generated panel-collapse icon.
src/generated/mistica-icons/icon-panel-collapse-light.tsx Adds new generated panel-collapse icon.
src/generated/mistica-icons/icon-panel-collapse-filled.tsx Adds new generated panel-collapse icon.
src/generated/mistica-icons/icon-message-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-meatball-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-meatball-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-lamp-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-lamp-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-lamp-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-kebab-menu-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-information-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-file-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-equal-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-equal-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-equal-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-download-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-chevron-left-double-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-chevron-left-double-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-check-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-battery-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-archive-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-add-more-filled.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-add-bolton-regular.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-add-bolton-light.tsx Generated icon formatting update.
src/generated/mistica-icons/icon-add-bolton-filled.tsx Generated icon formatting update.
src/boxed.tsx Extracts/exports shouldShowBoxedBorder helper for reuse.
src/acceptance_tests/pagination-acceptance-test.tsx Adjusts aria-current assertions for Pagination acceptance test.
packages/generate-design-tokens/index.js Adds generated-file banner + fixes prettier config resolution by using file paths.
package.json Adds scripts (generate-design-tokens, import-mistica-icons, browse) and updates lint-staged patterns.
mistica-web.iml Simplifies IntelliJ module content definition.
jest.unit.config.js Excludes colocated stories/ from unit-test match patterns.
css/vivo.css Generated CSS update + adds generated-file banner.
css/vivo-evolution.css Generated CSS update (text tokens) + adds generated-file banner.
css/telefonica.css Generated CSS update + adds generated-file banner.
css/o2.css Generated CSS update + adds generated-file banner.
css/movistar.css Generated CSS update + adds generated-file banner.
css/mistica-common.css Generated CSS update + adds generated-file banner.
css/esimflag.css Generated CSS update + adds generated-file banner.
css/blau.css Generated CSS update + adds generated-file banner.
agents/README.md Adds documentation for agent skills directory layout and symlink strategy.
AGENTS.md Expands agent/repo conventions and workflow guidance.
.storybook/preview.tsx Fixes event handler registration/cleanup + improves controls UI.
.storybook/main.ts Includes new src/**/stories/**/*-story.tsx pattern.
.gitignore Ignores local spec cache directory and Claude local settings.
.eslintrc.yml Adds sidenav-bar-specific restricted import rules.
.claude/settings.json Adds Claude hook to confirm before git commit* commands.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

'use client';
import * as React from 'react';

const REDUCED_MOTION_QUERY = '(prefers-reduced-motion)';
Comment thread src/text-tokens.tsx
Comment on lines +356 to +360
// TODO WIP es/de/pt are drafts pending official localization sign-off; en is final.
export const sidenavExpand: TextToken = {
es: 'Expandir navegación',
en: 'Expand navigation',
de: 'Navigation ausklappen',

@Marcosld Marcosld left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd split the review as you proposed to ease the review process

Comment thread agents/skills/run-in-browser/SKILL.md Outdated
Comment thread src/__stories__/sidenav-bar-controlled-story.tsx
Comment thread src/navigation-shared.css.ts
Sidenav component, stories, unit/acceptance/screenshot tests, shared navigation
extraction (navigation-shared, mobile-navigation-menu), colocated-story wiring,
eslint import rule, and the @generated token-banner tooling. Rebased onto the
skins/icons update; AI harness lives on mbertamini/1592-ai-harness.
@brtbrt
brtbrt force-pushed the mbertamini/1592-sidenav-bar branch from 25eefa6 to f805683 Compare August 25, 2026 19:54
@brtbrt
brtbrt changed the base branch from master to mbertamini/1592-sidenav-bar-update-skins-and-icons August 25, 2026 19:58
Comment thread .storybook/main.ts Outdated
Comment thread .storybook/preview.tsx
Comment thread packages/generate-design-tokens/index.js Outdated
Comment thread packages/generate-design-tokens/README.md
Comment thread src/sidenav-bar.tsx
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx
/** Whether user can toggle collapsed state. @default true */
collapsible?: true;
/** Paints a custom collapse action in the header, instead of the default icon button. */
renderCollapseAction?: (props: SidenavCollapseActionRenderProps) => React.ReactNode;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need this prop, because we already have a header slot + controlled mode for those who want to render a custom controller, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to replace the collapsing button the exact space it would occupy by default. the rest is to render elsewhere actually. I'd keep it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think we can replicate exactly what renderCollapseAction does by just using the headerSlot. In that case we are adding unnecessary complexity to an already complex component and I would remove it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, not really, because slot has different vertical spacings. you cannot replicate this, otherwide it would be useless, as you said.
let's say what @Telefonica/mistica-design say. if they want both, it's not such a big problem. this is really not what complicates the code. if they agree, we'll remove it and simplify the thing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see! Can we just simplify and make the slot margin to top be same as the logo when there's no logo and current 32px when there's logo? Removing a prop is always simplifying the thing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't get me: it does simplifies it, but it's not what makes the codebase tangled in this very case. again: designers? what's your say

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean simplifying from an user point of view that has less props to work with

Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar-item.tsx Outdated
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx

const headerObserver = new IntersectionObserver(
([entry]) => {
setShowHeaderDivider(!entry.isIntersecting);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should rename these to something related to scroll so it is easier to follow?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm I cannot make this work... what is it for? Perhaps we should let the slot manage its own scroll?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm I cannot make this work

I don't understand this.

what is it for?

divider while scrolling content! I don't know how else describing this 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just asking what is this for... can you paste a playroom snippet?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see. The one for the footer doesn't seem to be working, also perhaps we should implement something more similar to the fixed footer (perhaps reuse code ?), because with your implementation shadow disappears weirdly when the content bounces:

Screen.Recording.2026-08-27.at.16.20.35.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the footer is not in the specs. I asked them to address it in a call, but I don't see it. maybe they don't want it for a good reason? we can iterate it.

bouncing: I'll try fix it, I guess we should change the place it's rendered and it could take me a lot of time without AI

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From reading those lines I understood it should behave the same but maybe I'm mistaken @Telefonica/mistica-design ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Marcosld what are you using? I see it "behaving well" on firefox + safari + chromium

demo-scrolling.mov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:O I am using edge... but it also happens in chrome for me

Comment thread src/__stories__/sidenav-bar-default-story.tsx
Comment thread src/sidenav-bar.tsx
}, []);

// The rail is travelling between its two widths. See `columnsWhileMoving`.
const isMoving = collapsed !== collapsedSettled;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this logic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long story short: the CSStransition's events are not enough, we do need this to keep things well rendered while animating things

Comment thread src/sidenav-bar.tsx
Comment thread src/sidenav-bar.tsx Outdated
Comment on lines +628 to +638
const doublePanelItem =
doublePanel && panelOpenForItemId && sections
? findFirstLevelItem(sections, panelOpenForItemId)
: undefined;
const doublePanelChildren = doublePanelItem?.children;
const isDoublePanelOpen = Boolean(doublePanelChildren?.length);

if (isDoublePanelOpen && doublePanelItem && doublePanelChildren) {
lastDoublePanelContentRef.current = {label: doublePanelItem.label, children: doublePanelChildren};
}
const doublePanelContent = lastDoublePanelContentRef.current;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this logic for?

@brtbrt brtbrt Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Animation concerns. The double panel uses a CSSTransition with unmountOnExit. So the panel does not vanish when you close it. it slides away over the close duration, and it still renders during that slide.

My own rule of thumb in general: derived or cached values that never drive the screen by themselves go in a ref, but values that the screen must follow go in a state. (and thank you for reading this non-ai generated lengthy thing!)

in detail the sequence is:

  1. The user closes the column, so panelOpenForItemId becomes null.
  2. doublePanelItem and doublePanelChildren derive from that state, so both become undefined at once
  3. The CsSTransition does not unmount the column at once. it slides to zero within a defined time
  4. BUT During that slide, the column still renders! Without the ref, it would have no label and no children, so it would slide away as an empty band.

So, while the column is open, the ref acts like a snapshot of the current label and children. When the column closes, the render reads lastDoublePanelContentRef.current and paints the stale content until the movement ends and unmountOnExit removes the node.

I can see you already asking: so why not considering a state? Well I've considered it (and asked the AI the same actually). Short answer: this saves useless renders. A state would store the same value and schedule one extra render for nothing. The write during the render is safe because it only caches what the current render already shows.

@Marcosld Marcosld Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a simple code justifies having extra renders, few extra renders won't impact performance. This code along with previousSelection state var is a smell to me, something looks to be off. I think not having a panelOpen state var is the problem. We should have a panelOpenItemId state var and a panelOpen state var. That way we can toggle panel with panelOpen while just keeping the panelOpenItemId set (thus not needing the ref).

@brtbrt brtbrt Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of defeats the point of agentic coding: getting good code for the same effort. A more semantic const name might've made the actual purpose obvious. But if you'd rather add the extra renders, sure, let's go with it.

Comment thread src/sidenav-bar-motion.tsx
Comment thread src/__stories__/sidenav-bar-story-page.tsx Outdated
onSelectedItemIdChange?: (id: string | null) => void;
};

const SidenavBarContext = React.createContext<SidenavBarContextValue>({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these contexts? Can we just pass props down?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props drill hell, I'd be stick with this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the solution is overcomplicated, the sidenav should render its items and just pass the prop 1 boundary down

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice to have, but I wouldn't give high priority right now. I'll give priority to API compliance and correctness of the behavior. we can iterate later

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm not nice to have, I would simplify this so next person that needs to makes changes can understand it in less time (or even an AI)

@brtbrt brtbrt Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, not against it, I can give it a thought (and I need to analyse this, because I think it's not simply a one level prop to pass, but I can be wrong. I need to see the full picture in my head, which I currently don't have on this), but it takes time and we do got a deadline, which is expiring today. prioritizing things is key in this case. that's why I prefer iterate later, or as a one of the lastest thing to do in the list!

@brtbrt brtbrt Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep this converstion open and if I got time for this I'll give it some love!

Comment thread src/sidenav-bar-item.tsx Outdated
Comment thread src/index.tsx
Comment thread playroom/snippets.tsx Outdated
Comment thread playroom/snippets.tsx Outdated
Comment thread playroom/snippets.tsx Outdated
Comment thread playroom/snippets.tsx Outdated
Comment thread playroom/snippets.tsx Outdated
Comment thread src/sidenav-bar.css.ts Outdated
Comment thread src/sidenav-bar.css.ts Outdated
Comment thread src/sidenav-bar.css.ts Outdated
Comment thread src/sidenav-bar-section.tsx Outdated
Comment thread src/sidenav-bar-motion.tsx
@brtbrt
brtbrt requested a review from yceballost August 27, 2026 13:46
Comment thread src/sidenav-bar-item.tsx Outdated
@Telefonica Telefonica deleted a comment from github-actions Bot Aug 27, 2026

@brtbrt brtbrt left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

answered and/or fixed the pending comments

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Screenshot tests report

✔️ All passing

@yceballost
yceballost requested a review from aweell August 27, 2026 16:39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is about technical things from a designer, just take it with puntillas

but the usage of <SidenavLayout.Sidenav> is really strange for my experience using Mística in Playroom with other types of elements. In my mind, this usage is quite out of the conventions (maybe, I'm full wrong, but I need to say haha)

@brtbrt brtbrt Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok! what would you expect? write down a draft/idea here and let's adjust the DX. I think marcos also has some doubts about this API

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to figure out what you expect by looking at the other "layouty" components. let me know if you see a better fit

Comment thread playroom/snippets.tsx
Comment thread src/sidenav-bar.tsx
Comment thread src/__stories__/sidenav-bar-default-story.tsx
@brtbrt
brtbrt requested a review from yceballost August 27, 2026 17:14
};

export default {
title: 'Components/SidenavBar/Layout',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe is better to move this story to the layouts section? - Layouts/SidenavBar/

Like the others layouts 🤔
https://mistica-j7kpk4qd3-flows-projects-65bb050e.vercel.app/?path=/story/layout-responsive-layout--default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meh, I wouldn't do that. this is a side navbar related thing that you cannot use as a general layout. the rest of layouts can (and you can use the sidenav bar in it)

Comment thread src/__stories__/sidenav-bar-default-story.tsx
Comment thread src/sidenav-bar.tsx

const headerObserver = new IntersectionObserver(
([entry]) => {
setShowHeaderDivider(!entry.isIntersecting);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From reading those lines I understood it should behave the same but maybe I'm mistaken @Telefonica/mistica-design ?

Comment thread src/sidenav-bar.tsx
<div
className={classnames(styles.mainColumn, {
[styles.columnSeparator[normalizedVariant]]:
isDoublePanelOpen || isDoublePanelMounted,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDoublePanelMounted should be enough right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

almost yes, but for animation reasons we must keep both

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you elaborate, please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long story short: no, isDoublePanelMounted is not enough when closing it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is for when the panel is appearing instead, but as onEnter is applied before enter-active classes apply that var alone should suffice 🤔

Comment thread src/sidenav-bar.tsx Outdated
Comment on lines +628 to +638
const doublePanelItem =
doublePanel && panelOpenForItemId && sections
? findFirstLevelItem(sections, panelOpenForItemId)
: undefined;
const doublePanelChildren = doublePanelItem?.children;
const isDoublePanelOpen = Boolean(doublePanelChildren?.length);

if (isDoublePanelOpen && doublePanelItem && doublePanelChildren) {
lastDoublePanelContentRef.current = {label: doublePanelItem.label, children: doublePanelChildren};
}
const doublePanelContent = lastDoublePanelContentRef.current;

@Marcosld Marcosld Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a simple code justifies having extra renders, few extra renders won't impact performance. This code along with previousSelection state var is a smell to me, something looks to be off. I think not having a panelOpen state var is the problem. We should have a panelOpenItemId state var and a panelOpen state var. That way we can toggle panel with panelOpen while just keeping the panelOpenItemId set (thus not needing the ref).

onSelectedItemIdChange?: (id: string | null) => void;
};

const SidenavBarContext = React.createContext<SidenavBarContextValue>({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm not nice to have, I would simplify this so next person that needs to makes changes can understand it in less time (or even an AI)

Comment thread src/__stories__/sidenav-bar-story-page.tsx Outdated
Comment thread src/sidenav-bar-motion.tsx
Comment thread src/sidenav-bar.tsx Outdated
Comment thread src/sidenav-bar.tsx
</>
)}
</div>
{footerSlot && fixedFooter && (
Comment thread src/sidenav-bar.css.ts

// Panel (Dialog and Double Panel) -----------------------------------------------

export const dialogPanel = style({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Items content and title are not aligned in dialog panel

Current:

Image

Expected:

Image

Comment thread src/sidenav-bar.css.ts
const SECTION_TITLE_INSET = 24;
// Vertical space between two first-level entries of the body (section to section, section to
// stand-alone item, or stand-alone item to stand-alone item). Items inside a section stay adjacent.
const FIRST_LEVEL_GAP = 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is 16

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/sidenav-bar.css.ts
indicator: skinVars.colors.controlActivatedBrand,
hover: skinVars.colors.backgroundContainerBrandHover,
pressed: skinVars.colors.backgroundContainerBrandPressed,
selected: skinVars.colors.backgroundSelectedBrand,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not working in cyber

Image

@brtbrt
brtbrt requested a review from a team August 31, 2026 11:09
@brtbrt brtbrt self-assigned this Aug 31, 2026
Comment thread src/sidenav-bar.tsx
Comment on lines +530 to +532
isDoublePanelOpen &&
doublePanelItem &&
doublePanelChildren &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDoublePanelOpen already implies the other two conditions, right?

Comment thread src/sidenav-bar.tsx
Comment on lines +261 to +269
const closePanelForSelection = React.useCallback(
(selectionId: string | null) => {
if (doublePanel) {
setDismissedSelection(selectionId);
}
setPanelOpenForItemId(null);
},
[doublePanel]
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic attached to this function is dead, we are not reopening the panel on navigations from outside in controlled mode, and I think that's the right behaviour. You can remove all related code and probably this prop from the context altogether and just use setPanelOpenForItemId from sidenav items.

Comment thread src/sidenav-bar.tsx
Comment on lines +298 to +313
// The column closes as soon as the double panel mode goes off. Only the change of the mode closes it:
// the collapsed rail opens its dialog panel through this same state with the mode off.
const [previousDoublePanel, setPreviousDoublePanel] = React.useState(doublePanel);
if (doublePanel !== previousDoublePanel) {
setPreviousDoublePanel(doublePanel);
if (!doublePanel) {
setPanelOpenForItemId(null);
}
}

// A change of the entries invalidates the open column, whose parent item may not exist anymore.
const [previousSectionsLength, setPreviousSectionsLength] = React.useState(sections?.length ?? 0);
if ((sections?.length ?? 0) !== previousSectionsLength) {
setPreviousSectionsLength(sections?.length ?? 0);
setPanelOpenForItemId(null);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove these corner cases handling... Perhaps the parent can use key prop to force rerender if this actually happens... wdyt?

/** Variant of the top bar. The panel always renders in the default variant. */
variant: NonDeprecatedVariant;
/** Logo of the top bar. */
logo?: SidenavLogo;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see an inconsistency between mainnavigationBar and sidenav logo management

Using undefined as value in logo props in boths components
Example

image

Comment thread src/sidenav-bar.css.ts
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 32,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont know if this is the space.. but this space should be 24px. The same as 24px in bottom padding

An example to understand this. enable and disable Header Slot to see the difference with the red line

image image image

@AnaMontes11

Example

Comment thread src/sidenav-bar-types.ts
* ]
* }
*/
type SidenavSection = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a weird spacing between divider and icon when the sidenav is collapse and section titles have values
image

disabling section titles in this example you fix this problem. So, when the sidenav is collapse, section title in the sidenav should be hidden

example

fixedFooter: boolean;
boxed: boolean;
divider: boolean;
sectionDividerTop: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing a control to show/hide section titles

Comment thread src/sidenav-bar.tsx
*/
type SidenavCollapseActionRenderProps = {
/** Current collapsed state, which gives the direction of the action. */
collapsed: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you use :

collapsible={true}
collapsed={false}

The icon appear but is not working (logic)... but, why not hide the icon when collapsed is false? 🤔

Comment thread src/sidenav-bar.tsx
*/
type SidenavCollapseActionRenderProps = {
/** Current collapsed state, which gives the direction of the action. */
collapsed: boolean;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be defaultCollapsed a better naming for this?

Comment thread src/sidenav-bar-types.ts
/** Display text (truncated if too long). */
label: string;
/** Icon component or element. Required for top-level items in collapsed sidenav. */
asset?: ((props: IconProps) => JSX.Element) | React.ReactElement;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asset is optional.. but when you have collapsable in true, asset is required to avoid "ghost" items in the collapsed sidenav.

Is this something to aware in storybook? or any idea to avoid the wrong usage?

image

},
});

export const logoContainer = style([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Logo container is not adapting to height, should be? IMO yes. If the height of the logo is 72, this container height should be 72, fitting the height

Comment thread src/sidenav-bar-item.tsx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

ellipsis is an accessibility problem

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnaMontes11 this should be covered in specs

Comment thread src/sidenav-bar.tsx
/** Custom content below logo/collapse in header. */
headerSlot?: React.ReactNode;
/** Custom background colors for header (opaque), body (any), and footer (opaque) regions. */
background?: SidenavBarBackgroundColors;

@yceballost yceballost Aug 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this background is not working, right?

Add screenshot test for this case

Comment thread src/sidenav-bar.tsx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focus ring is overflowing
image
image
image

Comment thread src/sidenav-bar.css.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focus ring is not activating the hover state background

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI AI Generated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants