Skip to content

Conversation

@ivyolamit
Copy link
Contributor

@ivyolamit ivyolamit commented Oct 3, 2025

Summary:

Refactor Keypad to use WB Tabs instead of Tabbar:

  1. Add the WB Tabs component in Perseus
  2. "DISMISS" which is the close button of the Keypad now uses the IconButton instead of the dismiss tab. Popover close button was also considered but turns out the Popover is only for the Expression widget usage and other usages like in mobile doesn't have the Popover.
  3. Code Refactor to use WB Tabs in Keypad component
  4. Delete unused Tabbar component

Issue: LEMS-3361

Limitations

  1. Tabs component currently has a bug on tab navigation and is being fixed by the team.
  2. Will have a follow-up PR for the tab navigation for the close button.
Before After
image image

Test plan:

  1. Confirm that the Expression widget still looks and works as expected
  2. Confirm that the MathInput still looks and works as expected

Co-Authored by Claude Code

@ivyolamit ivyolamit self-assigned this Oct 3, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🗄️ Schema Change: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2025

Size Change: -467 B (-0.09%)

Total Size: 499 kB

Filename Size Change
packages/math-input/dist/es/index.js 98.7 kB -535 B (-0.54%)
packages/perseus/dist/es/index.js 204 kB +68 B (+0.03%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.8 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 5.98 kB
packages/math-input/dist/es/strings.js 1.61 kB
packages/perseus-core/dist/es/index.item-splitting.js 13.1 kB
packages/perseus-core/dist/es/index.js 22.4 kB
packages/perseus-editor/dist/es/index.js 97.9 kB
packages/perseus-linter/dist/es/index.js 7.68 kB
packages/perseus-score/dist/es/index.js 9.2 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/perseus/dist/es/strings.js 7.73 kB
packages/pure-markdown/dist/es/index.js 1.39 kB
packages/simple-markdown/dist/es/index.js 6.71 kB

compressed-size-action

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🛠️ Item Splitting: No Changes ✅

@ivyolamit ivyolamit added the project agnostic PRs reviewable by any Perseus team member label Oct 6, 2025
@ivyolamit ivyolamit force-pushed the LEMS-3361/use-wb-tab-in-expression branch from ee14db4 to 714a009 Compare November 12, 2025 21:50
@github-actions
Copy link
Contributor

github-actions bot commented Nov 12, 2025

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (6ea88ca) and published it to npm. You
can install it using the tag PR2952.

Example:

pnpm add @khanacademy/perseus@PR2952

If you are working in Khan Academy's frontend, you can run the below command.

./dev/tools/bump_perseus_version.ts -t PR2952

If you are working in Khan Academy's webapp, you can run the below command.

./dev/tools/bump_perseus_version.js -t PR2952

@ivyolamit ivyolamit force-pushed the LEMS-3361/use-wb-tab-in-expression branch from 4187e5a to e8abe23 Compare November 15, 2025 01:08
Copy link
Contributor Author

@ivyolamit ivyolamit Nov 15, 2025

Choose a reason for hiding this comment

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

This used to be inside the Keypad component. Moved it to a utility function to dry the Keypad code. Intention is still the same as the original but the logic is now follows the props required for the WB Tabs component.


return (
<div style={{padding: "1rem 2rem"}}>
<div style={{padding: "1rem 2rem", minHeight: "400px"}}>
Copy link
Contributor Author

@ivyolamit ivyolamit Nov 15, 2025

Choose a reason for hiding this comment

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

added minHeight here so that the keypad will be visible, current height doesn't show the entire keypad

Image

@ivyolamit ivyolamit marked this pull request as ready for review November 15, 2025 01:20
Copy link
Contributor

@handeyeco handeyeco left a comment

Choose a reason for hiding this comment

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

It's a huge win when we can use someone else's code instead of maintaining our own version of things! Thanks for digging into this. 🌈

In the future it might be helpful to try stacked PRs. This PR accomplishes a lot but not everything is related so it's can be hard to follow.

import {CursorContext} from "../../input/cursor-contexts";

import type {MathInputStrings} from "../../strings";
import type {MathInputStrings} from "@khanacademy/math-input/strings";
Copy link
Contributor

@handeyeco handeyeco Nov 17, 2025

Choose a reason for hiding this comment

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

Packages shouldn't import from themselves. I was thinking we had a lint rule for this 🤔

math-input shouldn't import from @khanacademy/math-input

@@ -0,0 +1,5 @@
export {getAvailableTabs} from "./get-available-tabs";
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really get the point of the index.ts file here. Now there are two ways to import these:

import {getAvailableTabs} from "./utils"
import {getAvailableTabs} from "./utils/get-available-tabs"

IMO, for the sake of consistency, it would be better to only have one way.

@@ -1,5 +1,3 @@
/* eslint-disable @khanacademy/ts-no-error-suppressions */
Copy link
Contributor

Choose a reason for hiding this comment

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

Why add a folder for math-input if it only contains the math-input.tsx file? Doesn't seem like the folder is needed but, if it is, maybe it would make sense for this file to be index.ts so that we don't have to update imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! this was a left over of my initial refactor attempt. I'll update that.

import Clickable from "@khanacademy/wonder-blocks-clickable";
import {View} from "@khanacademy/wonder-blocks-core";
import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
Copy link
Contributor

Choose a reason for hiding this comment

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

(comment on file/dir, not this line of code)

I don't think we need to add a math-input folder if the only file in it is just math-input.tsx. If for some reason we do need the math-input folder, maybe it would make sense for this file to be index.tsx so we don't need to update imports.

setSelectedPage(newSelectedPage as KeypadPageType);
}}
styles={{
tab: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move these styles out of the JSX rendering? If we move them out of the component it will make the JSX easier to read and we'll get an added bonus in that we won't have to redeclare them every time we rerender.


handleKeypadPress: (key: KeypadKey, e: any) => void = (key, e) => {
// Handle dismiss key to close the keypad
if (key === "DISMISS") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need a "DISMISS" key? Seems like that's obsolete now if we're using WB.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately yes,

  • Initial approach was using the WB Tabs for the x button and had it styled to be positioned on the right most away from the other tabs, that didn't looked so good
  • Second approach is using Popover x button but had to remove that solution because other usages of the keypad doesn't have a popover
  • So i added back in the DISMISS key and hooked it in IconButton and positioned it at the right of the Tabs component

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@handeyeco we do have potential code that we can remove though related to DIMISS. I'm just not confident if it's safe to remove now that we use the WB IconButton, you might have more context to this:

import type {KeypadProps} from "./keypad";
import type {KeypadPageType} from "../../types";

export type KeypadFractionsOnlyProps = Pick<
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me why we need both KeypadFractionsOnly and FractionsPage. Couldn't KeypadFractionsOnly just be part of Keypad?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

During the refactor i've separated the logic

  1. Displaying of the fractions only without any tabs
  2. Displaying of fractions together with other tabs

Both of this uses the FractionsPage, the first one is rendered directly from the KeyPad while the second one is is rendered via the util getAvailableTabs which will form the props that will be provided to the WB Tabs component.

Initially with #1 I added it in the Keypad component, but since it has a number of lines of code i decided to make it a different component so it will be easier to read in Keypad component like this (making the code dry):

// We don't want to show any available tabs on the fraction keypad
    if (fractionsOnly) {
        return <KeypadFractionsOnly {...props} />;
    }

import type {PerseusAnalyticsEvent, KeypadKey} from "@khanacademy/perseus-core";

type Props = {
export interface KeypadProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious: is there a reason for switching from type to interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our team had a discussion about this preference using interface.

@ivyolamit
Copy link
Contributor Author

@ivyolamit ivyolamit requested a review from handeyeco November 17, 2025 23:17
@ivyolamit
Copy link
Contributor Author

ivyolamit commented Nov 17, 2025

It's a huge win when we can use someone else's code instead of maintaining our own version of things! Thanks for digging into this. 🌈

In the future it might be helpful to try stacked PRs. This PR accomplishes a lot but not everything is related so it's can be hard to follow.

Thanks @handeyeco for the review and i'll take note on your suggestion for stacking the PRs in the future 🙏🏼
I've updated the PR to address your comments and this is the latest ZND. When you have time please have another look 🙏🏼


// We don't want to show any available tabs on the fraction keypad
if (fractionsOnly) {
return <KeypadFractionsOnly {...props} />;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a huge fan of the name. What about FractionKeypad? If we're splitting out FractionKeypad, I wonder if it would make sense to also split out a ExpressionKeypad too. Then Keypads return could be something like

return fractionsOnly ? <FractionKeypad {...props} /> : <ExpressionKeypad {...props} />

Although I'm not a fan of spreading props either. For instance you're still passing advancedRelations to FractionKeypad even though it's not used (and the type system doesn't allow using it). I think it makes more sense to explicitly pass props except in places where you really want to pass all props (like in forwardRef).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @handeyeco i'll look into it and do a follow-up PR.

ivyolamit added a commit that referenced this pull request Nov 19, 2025
## Summary:
Temporary add code ownership to math-input and keypad while keypad refactor is ongoing in #2952

Issue: LEMS-3361

## Test plan:

Author: ivyolamit

Reviewers: anakaren-rojas, mark-fitzgerald

Required Reviewers:

Approved By: anakaren-rojas, mark-fitzgerald

Checks: ✅ 10 checks were successful, ⏭️  1 check has been skipped

Pull Request URL: #3051
@ivyolamit ivyolamit force-pushed the LEMS-3361/use-wb-tab-in-expression branch from 939212e to 6c5d1e3 Compare November 19, 2025 20:08
ivyolamit added a commit that referenced this pull request Nov 20, 2025
## Summary:
Add Tabs and IconButton packages for math-input/keypad refactor

Context: Extracted package addition change from #2952 to minimize the risk of merge conflict since we will be waiting for Keypad QA.

Issue: LEMS-3361

## Test plan:

Author: ivyolamit

Reviewers: nishasy, ivyolamit

Required Reviewers:

Approved By: nishasy

Checks: ✅ 10 checks were successful, ⏭️  1 check has been skipped

Pull Request URL: #3054
@ivyolamit ivyolamit force-pushed the LEMS-3361/use-wb-tab-in-expression branch from 13f025e to 6ea88ca Compare November 20, 2025 19:53
@ivyolamit
Copy link
Contributor Author

👆🏼 Rebased from latest master which includes the adding for the packages, to minimize the merge conflict later as package files are often updated.
See: #3054

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

Labels

olc-5.0.853e0 project agnostic PRs reviewable by any Perseus team member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants