Skip to content
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

LabeledField: integrate with field components and fixes #2399

Merged
merged 19 commits into from
Dec 19, 2024

Conversation

beaesguerra
Copy link
Member

@beaesguerra beaesguerra commented Dec 16, 2024

Summary:

Putting LabeledField work together with field updates and addressing some issues. Fixes include:

TextField and TextArea:

  • Set aria-required based on required prop
  • Always clear error state onChange if instantValidation is false so that external error messages set using the error prop can still be cleared (This is useful for clearing any backend errors when the input value changes)

LabeledField updates:

  • Let required prop be a boolean or string so it can be passed down to the field prop
  • Conditionally set spacing above the error message depending on if there is an error message
  • Set required, error and light props for LabeledField and field component if it is set on either LabeledField or field component
  • Rename error prop to errorMessage (This technically isn't a breaking change since the batch of labeled field work hasn't been released yet, labeled field work is currently in the feature/labeled-field branch)

Stories/docs:

  • Add docs on best practices for forms and fields
  • Add docs around LabeledTextField deprecation in favour of LabeledField + TextField
  • Update LabeledField examples and docs. Includes examples for validation and moving focus to the first field that has an error message after form submission

Issue: WB-1783

Test plan:

  1. Review new docs:
  • LabeledField (?path=/docs/packages-labeledfield--docs)
  • LabeledTextField (/?path=/docs/packages-form-labeledtextfield-deprecated--docs)
  • Form Best Practices (?path=/docs/packages-form-overview--docs)
  1. Test LabeledField with Field Validation: Confirm validation works as expected
  • LabeledField Required story (?path=/story/packages-labeledfield--required)
    • Tabbing through fields without entering anything
    • Submitting without filling in fields
    • Inputting/selecting a value and removing it
  • LabeledField Validation story (?path=/story/packages-labeledfield--validation)
    • Triggering validation errors
    • Clearing validation errors when selected value changes
    • Submitting the form to see an example for backend errors

Note: Did some preliminary testing with LabeledField and new validation features. Different screen reader and browser combinations had different behaviours around announcing errors so I created WB-1842 for more testing + documentation.

@beaesguerra beaesguerra self-assigned this Dec 16, 2024
Copy link

changeset-bot bot commented Dec 16, 2024

🦋 Changeset detected

Latest commit: 506cbd7

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

This PR includes changesets to release 5 packages
Name Type
@khanacademy/wonder-blocks-form Patch
@khanacademy/wonder-blocks-labeled-field Patch
@khanacademy/wonder-blocks-search-field Patch
@khanacademy/wonder-blocks-dropdown Patch
@khanacademy/wonder-blocks-birthday-picker Patch

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 Dec 16, 2024

Size Change: +89 B (+0.09%)

Total Size: 98.8 kB

Filename Size Change
packages/wonder-blocks-labeled-field/dist/es/index.js 1.93 kB +89 B (+4.83%) 🔍
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.77 kB
packages/wonder-blocks-banner/dist/es/index.js 1.53 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.77 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 887 B
packages/wonder-blocks-button/dist/es/index.js 4.04 kB
packages/wonder-blocks-cell/dist/es/index.js 2.01 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.06 kB
packages/wonder-blocks-core/dist/es/index.js 3.52 kB
packages/wonder-blocks-data/dist/es/index.js 6.24 kB
packages/wonder-blocks-dropdown/dist/es/index.js 19.1 kB
packages/wonder-blocks-form/dist/es/index.js 6.2 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-icon-button/dist/es/index.js 2.95 kB
packages/wonder-blocks-icon/dist/es/index.js 871 B
packages/wonder-blocks-layout/dist/es/index.js 1.82 kB
packages/wonder-blocks-link/dist/es/index.js 2.28 kB
packages/wonder-blocks-modal/dist/es/index.js 5.42 kB
packages/wonder-blocks-pill/dist/es/index.js 1.65 kB
packages/wonder-blocks-popover/dist/es/index.js 4.85 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.52 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.36 kB
packages/wonder-blocks-switch/dist/es/index.js 1.92 kB
packages/wonder-blocks-testing-core/dist/es/index.js 3.74 kB
packages/wonder-blocks-testing/dist/es/index.js 1.07 kB
packages/wonder-blocks-theming/dist/es/index.js 693 B
packages/wonder-blocks-timing/dist/es/index.js 1.8 kB
packages/wonder-blocks-tokens/dist/es/index.js 2.36 kB
packages/wonder-blocks-toolbar/dist/es/index.js 905 B
packages/wonder-blocks-tooltip/dist/es/index.js 6.99 kB
packages/wonder-blocks-typography/dist/es/index.js 1.23 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Dec 16, 2024

A new build was pushed to Chromatic! 🚀

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

Chromatic results:

Metric Total
Captured snapshots 21
Tests with visual changes 5
Total stories 525
Inherited (not captured) snapshots [TurboSnap] 362
Tests on the build 383

@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-labeled-field": patch
Copy link
Member Author

Choose a reason for hiding this comment

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

Note: Although renaming a prop is technically a breaking change, I marked this as a patch change since the LabeledField component isn't released yet (it's in the feature/labeled-field branch so the previous prop hasn't been available for consumers. Let me know if there are any concerns with this!

Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me, thanks for the clarification!

@@ -1,34 +0,0 @@
import {Meta, Story, Canvas} from "@storybook/blocks";
Copy link
Member Author

Choose a reason for hiding this comment

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

I combined this page with the Overview page so guidelines are together

const field = screen.getByRole("textbox");
await userEvent.type(field, "test");
handleValidate.mockReset(); // Reset mock before leaving the field
Copy link
Member Author

Choose a reason for hiding this comment

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

Since we now clear the error state onChange when instantValidation=false (see use-field-validation.ts changes), onValidate is called each time a user types a character. We reset the mocks now before the action so we can more clearly check the onValidate calls triggered by the action

*
* Note: Since the error icon has an aria-label, screen readers will
* prefix the error message with "Error:" (or the value provided to the
* errorIconAriaLabel in the `labels` prop)
*/
error?: React.ReactNode;
errorMessage?: React.ReactNode;
Copy link
Member Author

Choose a reason for hiding this comment

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

Renaming to errorMessage so it is clear it's the message. (Form components like TextField, TextArea, etc have error boolean props so it could be confusing if this one continuted to be named error when it doesn't expect a boolean)

Comment on lines +134 to +136
const isRequired = !!required || !!field.props.required;
const hasError = !!errorMessage || !!field.props.error;
const isLight = light || field.props.light;
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 also check the field's props to see if LabeledField is required, is in an error state, or is in light mode.

This will make migration easier too so that we don't need to move light or required props from the field to the LabeledField component. The errorMessage prop would need to be set for LabeledField still since fields don't have this prop currently

Copy link
Member

Choose a reason for hiding this comment

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

question: Would this work in the case where we use custom fields? I'm not sure if this will fail of a custom field doesn't contain one of these props.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question!

If LabeledField and a custom field doesn't have the required/error/light props set, then these values will be false or undefined! (no issues here)

If a custom field doesn't support these props, then they won't be applied to the component. From testing out LabeledField with a plain <input> element, I get warnings around props not being supported or are the wrong type

image

Note: There is documentation around how LabeledField works best with field components that accept error and required props (I'll update this to include the light prop also) (?path=/docs/packages-labeledfield--docs#fields):
image

One idea is we could see if we can refine the field type so that the component must have these props supported.

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a story that shows LabeledField with a plain input element, let me know what you think!

Copy link
Member

Choose a reason for hiding this comment

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

One idea is we could see if we can refine the field type so that the component must have these props supported.

I think it is fine not to enforce those props in field instances (at least not for now). I'm sure there will be cases where we still have reusable UI components in webapp that could be combined with LabeledField (e.g. DatePicker, TimePicker, some deprecated text field/area components). If we get to a point where we can get rid of those deprecated versions, then it would be nice to enforce those props.

Maybe these errors/warnings could go away using the nullish coalescing operator (??)

e.g.

const isRequired = !!required || !!field.props.required ?? false;

Copy link
Member Author

@beaesguerra beaesguerra Dec 18, 2024

Choose a reason for hiding this comment

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

Maybe these errors/warnings could go away using the nullish coalescing operator (??)

This doesn't address it unfortunately! The required prop isn't as much of an issue since it is a supported html attribute. It's more of the other props since those are specific to WB components! I was trying to see if there's a way for us to check at runtime if a component supports certain props before we set it. I couldn't find anything around this (let me know though if we have a way to do this!)

Some other options:

  1. LabeledField only sets html attributes. LabeledField works best though when the field components can handle their own required/error/light states and their testId.
  2. Use a renderField prop instead so that the props are passed as an argument and consumers could decide how to apply the props to their custom element
  3. We support both the current field prop and a renderCustomField prop (see #2)
  4. We leave things as is with the console warnings and custom components need to support the prop

Let me know what you think or if you have other ideas!

Copy link
Member

Choose a reason for hiding this comment

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

Ahhh got it, thanks for clarifying. I'd say 4 would be best in this case as we still offer support to WB fields (making a good case for trying to use these components).

*/
required?: boolean;
required?: boolean | string;
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 update the required prop type to be the same as field components so it can be passed down

@beaesguerra beaesguerra marked this pull request as ready for review December 16, 2024 23:13
@khan-actions-bot khan-actions-bot requested a review from a team December 16, 2024 23:13
@khan-actions-bot
Copy link
Contributor

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to .changeset/chilly-mirrors-add.md, .changeset/metal-maps-move.md, .changeset/slow-otters-crash.md, .changeset/smart-grapes-serve.md, .changeset/spicy-rivers-marry.md, __docs__/wonder-blocks-form/_overview_.mdx, __docs__/wonder-blocks-form/accessibility.stories.tsx, __docs__/wonder-blocks-form/labeled-text-field.stories.tsx, __docs__/wonder-blocks-labeled-field/labeled-field.stories.tsx, packages/wonder-blocks-labeled-field/tsconfig-build.json, packages/wonder-blocks-form/src/components/text-area.tsx, packages/wonder-blocks-form/src/components/text-field.tsx, packages/wonder-blocks-form/src/hooks/use-field-validation.ts, packages/wonder-blocks-labeled-field/src/components/labeled-field.tsx, packages/wonder-blocks-form/src/components/__tests__/text-area.test.tsx, packages/wonder-blocks-form/src/components/__tests__/text-field.test.tsx, packages/wonder-blocks-labeled-field/src/components/__tests__/labeled-field.test.tsx

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 Dec 16, 2024

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (7c66299) 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="PR2399"

Packages can also be installed manually by running:

yarn add @khanacademy/wonder-blocks-<package-name>@PR2399

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 incredible! Just have a question about the logic for overriding the light, required, error props, but the rest looks really solid, great job 👏 🚀

@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-labeled-field": patch
Copy link
Member

Choose a reason for hiding this comment

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

sounds good to me, thanks for the clarification!

TextField, TextArea, Choice, Checkbox, RadioButton, etc.


## Best Practices
Copy link
Member

Choose a reason for hiding this comment

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

praise: Love this new consolidated section!

Copy link
Member Author

Choose a reason for hiding this comment

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

Also shared these docs with the design team to see if they have any feedback or would like to add any other guidelines! The content now is based on our cubby meeting discussion a while back

Comment on lines 47 to 50
- Avoid disabling form submission buttons. There could be exceptions if the
button is for one field.
- If there are errors after a form is submitted, programatically move the user's
focus to the first field with an error
Copy link
Member

Choose a reason for hiding this comment

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

thought: I wonder if we should include static images at some point to illustrate how this could look like. No changes necessary, just food for thought.

Copy link
Member Author

Choose a reason for hiding this comment

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

I included a Canvas of the LabeledField validation story so that there is an example of validation and focusing on the first field with an error after submission! ?path=/docs/packages-form-overview--docs

Copy link
Member

Choose a reason for hiding this comment

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

Nice, thanks!

@@ -6,7 +6,7 @@ import {TextArea} from "@khanacademy/wonder-blocks-form";
import {spacing} from "@khanacademy/wonder-blocks-tokens";

export default {
title: "Packages / Form / Accessibility",
title: "", // Empty title so that these examples don't show in the sidebar. Examples are referenced in the .mdx file
Copy link
Member

Choose a reason for hiding this comment

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

praise: Neat! I didn't know about this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Disregard this! It wasn't working as expected, the story was still there in another section if I scrolled down 😅 I found if I named these stories the same as the Overview title, it isn't shown in the sidebar

Comment on lines +134 to +136
const isRequired = !!required || !!field.props.required;
const hasError = !!errorMessage || !!field.props.error;
const isLight = light || field.props.light;
Copy link
Member

Choose a reason for hiding this comment

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

question: Would this work in the case where we use custom fields? I'm not sure if this will fail of a custom field doesn't contain one of these props.

@beaesguerra beaesguerra force-pushed the wb-1783-labeled-field-with-updates branch from 786157e to 42fd023 Compare December 17, 2024 22:24
@@ -308,3 +564,18 @@ export const Scenarios = (args: PropsFor<typeof LabeledField>) => {
</View>
);
};

/**
* Here is an example where LabeledField is used with a custom element.
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: What do you think about adding some NOTE here for folks to be aware that this is available but that we still suggest using WB fields as much as they can?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added some more notes around using non wb components and how there'll be warnings if props aren't supported by the custom component

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.

Looks great! thanks for clarifying the new props part. The current approach looks good to me 🚀

Comment on lines +134 to +136
const isRequired = !!required || !!field.props.required;
const hasError = !!errorMessage || !!field.props.error;
const isLight = light || field.props.light;
Copy link
Member

Choose a reason for hiding this comment

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

Ahhh got it, thanks for clarifying. I'd say 4 would be best in this case as we still offer support to WB fields (making a good case for trying to use these components).

]}
tag="label"
htmlFor={fieldId}
testId={testId && `${testId}-label`}
id={labelId}
>
{label}
{required && requiredIcon}
{isRequired && requiredIcon}
</LabelMedium>
<Strut size={spacing.xxxSmall_4} />
Copy link
Member

Choose a reason for hiding this comment

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

thought: Looking at the stories, this got me thinking..... we should probably refactor this at some point to use gap instead of Strut (as we have been discussing previously). No need to make any changes here, but this would be a really nice improvement in the future (for all WB components).

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch! I'll update this in a follow up PR :) Thanks!

@beaesguerra beaesguerra merged commit d9bc865 into feature/labeled-field Dec 19, 2024
14 checks passed
@beaesguerra beaesguerra deleted the wb-1783-labeled-field-with-updates branch December 19, 2024 21:43
Copy link

codecov bot commented Dec 19, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Please upload report for BASE (feature/labeled-field@5badeed). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                   Coverage Diff                   @@
##             feature/labeled-field   #2399   +/-   ##
=======================================================
  Coverage                         ?       0           
=======================================================
  Files                            ?       0           
  Lines                            ?       0           
  Branches                         ?       0           
=======================================================
  Hits                             ?       0           
  Misses                           ?       0           
  Partials                         ?       0           

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 5badeed...506cbd7. Read the comment docs.

beaesguerra added a commit that referenced this pull request Jan 16, 2025
… `main` (#2429)

## Summary:
This PR includes the following commits:
- LabeledField (part 1): Initial set up for LabeledField component based on FieldHeading (#2322)
- LabeledField(part2): refactor to a function component (#2338)
- LabeledField(part3): Wire up attributes (#2339)
- LabeledField(part4): styling and error icon (#2344)
- LabeledField: integrate with field components and fixes (#2399)
- Use LabeledField in other component stories (#2400)
- Add more docs around different browser + screen reader behaviours for LabeledField (#2403)
- LabeledField: Make sure custom required message is shown (#2425)
- LabeledField: Add a story for custom jsx for props (#2426)
- LabeledField: fix import (#2427)

Issue: WB-1499

## Test plan:
- Confirm LabeledField is working as expected `?path=/docs/packages-labeledfield--docs`
- Review docs for LabeledField: `?path=/docs/packages-labeledfield--docs`

Author: beaesguerra

Reviewers: beaesguerra, jandrade

Required Reviewers:

Approved By: jandrade

Checks: ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Check build sizes (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ gerald, ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Check build sizes (ubuntu-latest, 20.x), ⏭️  Publish npm snapshot, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ⏭️  Chromatic - Skip on Release PR (changesets), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ gerald, ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 20.x), ✅ Test / Test (ubuntu-latest, 20.x, 2/2), ✅ Test / Test (ubuntu-latest, 20.x, 1/2), ✅ Lint / Lint (ubuntu-latest, 20.x), ✅ Check build sizes (ubuntu-latest, 20.x), ⏭️  Publish npm snapshot, ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 20.x), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ⏭️  Chromatic - Skip on Release PR (changesets), ✅ gerald, ⌛ undefined

Pull Request URL: #2429
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.

3 participants