Skip to content

Tabs: Styling #2545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: feature/tabs
Choose a base branch
from
Open

Tabs: Styling #2545

wants to merge 13 commits into from

Conversation

beaesguerra
Copy link
Member

@beaesguerra beaesguerra commented Apr 8, 2025

Summary:

  • Implement styling for the Tabs component
  • Add support for styles prop
  • Implement the selected tab indicator animation with animated prop
    • Refactored NavigationTabs tab indicator logic into a hook so it can be shared

Issue: WB-1908

Test plan:

  • styles look as expected
    • Scenarios (including scrolling behaviour) (?path=/story/packages-tabs-tabs--scenarios)
    • Tab All Variants (?path=/story/packages-tabs-tabs-subcomponents-tab-all-variants--default)
    • Tabs All Variants (states + zoom) (?path=/story/packages-tabs-tabs-tabs-all-variants--default)
  • animated prop works as expected (?path=/story/packages-tabs-tabs--animated)
  • styles prop works as expected (?path=/story/packages-tabs-tabs--custom-styles)
  • NavigationTabs animations continues to work as expected

Implementation Plan

Copy link

changeset-bot bot commented Apr 8, 2025

🦋 Changeset detected

Latest commit: e6cb053

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@khanacademy/wonder-blocks-tabs Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Apr 8, 2025

Size Change: +643 B (+0.62%)

Total Size: 105 kB

Filename Size Change
packages/wonder-blocks-tabs/dist/es/index.js 3.88 kB +643 B (+19.89%) 🚨
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.54 kB
packages/wonder-blocks-announcer/dist/es/index.js 2.04 kB
packages/wonder-blocks-banner/dist/es/index.js 1.56 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.88 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 886 B
packages/wonder-blocks-button/dist/es/index.js 4.32 kB
packages/wonder-blocks-cell/dist/es/index.js 2.35 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.07 kB
packages/wonder-blocks-core/dist/es/index.js 2.88 kB
packages/wonder-blocks-data/dist/es/index.js 6.25 kB
packages/wonder-blocks-dropdown/dist/es/index.js 19.8 kB
packages/wonder-blocks-form/dist/es/index.js 6.04 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-icon-button/dist/es/index.js 2.31 kB
packages/wonder-blocks-icon/dist/es/index.js 873 B
packages/wonder-blocks-labeled-field/dist/es/index.js 1.26 kB
packages/wonder-blocks-layout/dist/es/index.js 1.82 kB
packages/wonder-blocks-link/dist/es/index.js 2.04 kB
packages/wonder-blocks-modal/dist/es/index.js 5.48 kB
packages/wonder-blocks-pill/dist/es/index.js 1.49 kB
packages/wonder-blocks-popover/dist/es/index.js 4.84 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.52 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.33 kB
packages/wonder-blocks-styles/dist/es/index.js 473 B
packages/wonder-blocks-switch/dist/es/index.js 2.02 kB
packages/wonder-blocks-testing-core/dist/es/index.js 3.91 kB
packages/wonder-blocks-testing/dist/es/index.js 1.07 kB
packages/wonder-blocks-theming/dist/es/index.js 679 B
packages/wonder-blocks-timing/dist/es/index.js 1.79 kB
packages/wonder-blocks-tokens/dist/es/index.js 2.82 kB
packages/wonder-blocks-toolbar/dist/es/index.js 923 B
packages/wonder-blocks-tooltip/dist/es/index.js 7.01 kB
packages/wonder-blocks-typography/dist/es/index.js 1.23 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Apr 8, 2025

A new build was pushed to Chromatic! 🚀

https://5e1bf4b385e3fb0020b7073c-onhdyxknjc.chromatic.com/

Chromatic results:

Metric Total
Captured snapshots 424
Tests with visual changes 16
Total stories 631
Inherited (not captured) snapshots [TurboSnap] 0
Tests on the build 424

@@ -0,0 +1,141 @@
import {
Copy link
Member Author

Choose a reason for hiding this comment

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

Most of this is the logic that was in NavigationTabs. Will point out things that are different to support both types of tabs


// Find the active tab
const activeTab = Array.from(tabsContainerRef.current.children).find(
isTabActive,
Copy link
Member Author

Choose a reason for hiding this comment

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

We make isTabActive a prop since we use aria-current in NavigationTabs and aria-selected in Tabs to determine if a tab is active.

@@ -2,12 +2,12 @@ import {
addStyle,
AriaProps,
StyleType,
useOnMountEffect,
Copy link
Member Author

Choose a reason for hiding this comment

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

Pulling out the logic for the animated selected tab indicator so it can be shared with Tabs.

I was starting to share the styles between Tabs and NavigationTabs, but found that the way the styles are applied is a bit different since their html structures are different.

NavigationTabs has extra nesting due to the ul and li elements:

<nav>
  <ul>
    <li>
      <a href="#">
        Link
      </a>
    </li>
    <li>
      <a href="#">
        Link
      </a>
    </li>
  </ul>
</nav>

Tabs:

<div>
  <div role="tablist>
    <button role="tab" />
    <button role="tab" />
  </div>
 <div role="tabpanel"/>
 <div role="tabpanel" />
</div>

Comment on lines +116 to +119
[":after" as any]: {
height: border.width.hairline,
backgroundColor:
semanticColor.action.secondary.progressive.hover.foreground,
Copy link
Member Author

Choose a reason for hiding this comment

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

Using :after styling to apply the hover/pressed underline styling instead of box-shadow because we use box-shadow for the focus outline.

In NavigationTabs, the Link and NavigationTabItem components were used to represent a tab, so we were able to use box-shadow for the hover/pressed underline styling since it was on the NavigationTabItem component instead of the Link (which has the focus styling)

Copy link
Member

Choose a reason for hiding this comment

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

This could be helpful as a summarized comment in the code.

FWIW I think it would be fine using box-shadow directly as we use a combination of box-shadow + outine for the global focus online. So using this approach would be only overriding the inner white ring, but the outer blue would remain there.

Do you think it would make sense to keep it this way?

</>
),
globals: {
zoom: "400%",
Copy link
Member Author

Choose a reason for hiding this comment

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

This is working as expected in Chromatic now! I need to follow up with the Chromatic support team still on the other flaky tests we were seeing before

return tabElement.ariaSelected === "true";
}, []);

const {indicatorProps, updateUnderlineStyle} = useTabIndicator({
Copy link
Member Author

Choose a reason for hiding this comment

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

This uses the same logic as NavigationTabs for the selected tab indicator animation.

I created https://khanacademy.atlassian.net/browse/WB-1924 for adding animations to the tab panel. This is something that won't be needed for NavigationTabs since that relies on link navigation.

@beaesguerra beaesguerra force-pushed the wb-1908-tabs-styles branch from ff93caf to 08039a9 Compare April 9, 2025 00:06
@beaesguerra beaesguerra marked this pull request as ready for review April 9, 2025 00:15
@khan-actions-bot khan-actions-bot requested a review from a team April 9, 2025 00:15
@khan-actions-bot
Copy link
Contributor

khan-actions-bot commented Apr 9, 2025

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to .changeset/dirty-pens-share.md, __docs__/components/placeholder.tsx, __docs__/wonder-blocks-tabs/tab-variants.stories.tsx, __docs__/wonder-blocks-tabs/tabs-variants.stories.tsx, __docs__/wonder-blocks-tabs/tabs.accessibility.mdx, __docs__/wonder-blocks-tabs/tabs.stories.tsx, packages/wonder-blocks-tabs/tsconfig-build.json, packages/wonder-blocks-tabs/src/components/navigation-tabs.tsx, packages/wonder-blocks-tabs/src/components/tab-panel.tsx, packages/wonder-blocks-tabs/src/components/tab.tsx, packages/wonder-blocks-tabs/src/components/tablist.tsx, packages/wonder-blocks-tabs/src/components/tabs.tsx, packages/wonder-blocks-tabs/src/hooks/use-tab-indicator.ts

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

Copy link
Contributor

github-actions bot commented Apr 9, 2025

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (3b49b81) and published all packages with changesets to npm.

You can install the packages in webapp by running:

./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2545"

Packages can also be installed manually by running:

pnpm add @khanacademy/wonder-blocks-<package-name>@PR2545

@beaesguerra beaesguerra force-pushed the wb-1908-tabs-styles branch from 7ca962c to e6cb053 Compare April 9, 2025 15:58
Copy link

codecov bot commented Apr 9, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 0.00%. Comparing base (56e694d) to head (e6cb053).

Additional details and impacted files

Impacted file tree graph

@@         Coverage Diff          @@
##   feature/tabs   #2545   +/-   ##
====================================
====================================

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 56e694d...e6cb053. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@jandrade jandrade left a comment

Choose a reason for hiding this comment

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

This looks great! I left some comments for you to consider, but nothing blocking that I could think of. Great stuff 👏 💯

Comment on lines +83 to +84
// TODO: Update to use spacing tokens
gap: "4px",
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: I guess this is possible now that Marcy has landed the new spacing tokens :).

<>
<AllVariants rows={rows} columns={columns}>
{(props) => (
<View>
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: This View could be removed if it is not necessary.

export default {
title: "Packages / Tabs / Tabs / Tabs",
title: "Packages / Tabs / Tabs",
Copy link
Member

Choose a reason for hiding this comment

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

yay 🎉

component: Tabs,
subcomponents: {Tab},
Copy link
Member

Choose a reason for hiding this comment

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

praise: Ohh this makes total sense! nice add.

Comment on lines +562 to +573
export const Scenarios: StoryComponentType = {
render: (args) => {
return (
<ScenariosLayout scenarios={scenarios}>
{(props) => <ControlledTabs {...args} {...props} />}
</ScenariosLayout>
);
},
args: {
animated: true,
},
};
Copy link
Member

Choose a reason for hiding this comment

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

thought: (no change required).... this is really useful for testing/internal purposes. How do you feel about creating a separate stories file to cover this? I'm not sure if we should include this as part of the Docs page, or if it is still useful for devs to understand/test. wdyt?

const isTabActive = React.useCallback(
(navTabItemElement: Element): boolean => {
// Check the link inside the NavigationTabItem to see if it has aria-current="page"
return navTabItemElement.children[0]?.ariaCurrent === "page";
Copy link
Member

Choose a reason for hiding this comment

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

question: Unrelated to this PR..... Would there be a case where the first element is NOT a Link? I don't think so, but want to confirm if this could be an edge case.

Comment on lines +116 to +119
[":after" as any]: {
height: border.width.hairline,
backgroundColor:
semanticColor.action.secondary.progressive.hover.foreground,
Copy link
Member

Choose a reason for hiding this comment

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

This could be helpful as a summarized comment in the code.

FWIW I think it would be fine using box-shadow directly as we use a combination of box-shadow + outine for the global focus online. So using this approach would be only overriding the inner white ring, but the outer blue would remain there.

Do you think it would make sense to keep it this way?

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

Successfully merging this pull request may close these issues.

3 participants