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

[FX-4599] Migrate OutlineInput #4363

Merged
merged 8 commits into from
Jun 28, 2024

Conversation

ruslan-sed
Copy link
Contributor

@ruslan-sed ruslan-sed commented Jun 13, 2024

FX-4599

Description

Migrate OutlinedInput to TailwindCSS.

How to test

  • Temploy
  • FIXME: Add the steps describing how to verify your changes

Note

Visual tests for Select with a focused state after click are failing. There is no box shadow and correct border. But in browser, they are. The issue occurs only in Cypress. I and @TomasSlama could not figure out for now the reason. Since it works in browser, we agreed, that we can merge it now and fix it in a follow-up ticket.

Development checks

Breaking change

  • codemod is created and showcased in the changeset
  • test alpha package of Picasso in StaffPortal

All development checks should be done and set checked to pass the
GitHub Bot: TODOLess action

PR commands

List of available commands:

  • @toptal-bot run package:alpha-release - Release alpha version
  • @toptal-anvil ping reviewers - Ping FX team for review
PR Review Guidelines

When to approve? ✅

You are OK with merging this PR and

  1. You have no extra requests.
  2. You have optional requests.
    1. Add nit: to your comment. (ex. nit: I'd rename this variable from makeCircle to getCircle)

When to request changes? ❌

You are not OK with merging this PR because

  1. Something is broken after the changes.
  2. Acceptance criteria is not reached.
  3. Code is dirty.

When to comment (neither ✅ nor ❌)

You want your comments to be addressed before merging this PR in cases like:

  1. There are leftovers like unnecessary logs, comments, etc.
  2. You have an opinionated comment regarding the code that requires a discussion.
  3. You have questions.

How to handle the comments?

  1. An owner of a comment is the only one who can resolve it.
  2. An owner of a comment must resolve it when it's addressed.
  3. A PR owner must reply with ✅ when a comment is addressed.

Copy link

changeset-bot bot commented Jun 13, 2024

🦋 Changeset detected

Latest commit: 5f0ced2

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

This PR includes changesets to release 16 packages
Name Type
@toptal/picasso-outlined-input Major
@toptal/picasso-forms Patch
@toptal/picasso-rich-text-editor Patch
@toptal/picasso Patch
@toptal/picasso-autocomplete Patch
@toptal/picasso-avatar-upload Patch
@toptal/picasso-date-picker Patch
@toptal/picasso-input Patch
@toptal/picasso-number-input Patch
@toptal/picasso-password-input Patch
@toptal/picasso-select Patch
@toptal/picasso-tagselector Patch
@toptal/picasso-timepicker Patch
@toptal/picasso-page Patch
@toptal/picasso-query-builder Patch
@toptal/picasso-date-select 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

@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch 12 times, most recently from 85ffac2 to 9a770cc Compare June 19, 2024 15:08
@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch 2 times, most recently from 0d7c2f6 to d49ccd6 Compare June 21, 2024 12:58
@ruslan-sed ruslan-sed marked this pull request as ready for review June 21, 2024 13:00
@ruslan-sed ruslan-sed requested a review from a team as a code owner June 21, 2024 13:00
@ruslan-sed
Copy link
Contributor Author

@toptal-anvil ping reviewers

1 similar comment
@ruslan-sed
Copy link
Contributor Author

@toptal-anvil ping reviewers

@@ -28,25 +28,22 @@
"@toptal/picasso-input-adornment": "1.0.7",
"@toptal/picasso-shared": "15.0.0",
"@toptal/picasso-utils": "1.0.3",
"@toptal/picasso-tailwind-merge": "1.0.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

let's move @toptal/picasso-tailwind-merge to peer dependencies and dev dependencies

packages/base/OutlinedInput/src/OutlinedInput/utils.ts Outdated Show resolved Hide resolved
packages/base/OutlinedInput/src/OutlinedInput/utils.ts Outdated Show resolved Hide resolved
Comment on lines 76 to 83
disabled && 'text-gray-600 [&::-webkit-text-fill-color]:text-gray-600',
// On Safari the text gets a bit lighter as if it had some transparency applied to it
// We need this webkit-specific property to achieve the exact font color
disabled && '[&::-webkit-text-fill-color]:text-gray-600',
// It's a special case for TimePicker
type === 'time' && inputProps?.disabled && 'text-black',
isDark &&
'text-white [&::placeholder]:text-white [&::placeholder]:opacity-[0.64]',
Copy link
Contributor

Choose a reason for hiding this comment

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

So if we have disabled dark input, it should still have white text color? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because styles for disabled goes after this line, so twMerge will remove text-white.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry. My previous answer was incorrect. Correct answer is - yes. And It is so in master branch.

)

// eslint-disable-next-line complexity
export const getInputClassName = ({
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 sure I like these big functions that deal with everything 🤔 Imagine you would have to test this function, it would be very painful. I prefer smaller functions with a single responsibility and with few inputs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since we already have visual tests I was thinking about this function as something we will need to test. Its purpose here is just to decrease the complexity of the component. Sure, we can split it into smaller functions, but will it give us any benefit, considering that the effect of this function is already covered with more high-level tests?

Copy link
Contributor Author

@ruslan-sed ruslan-sed Jun 25, 2024

Choose a reason for hiding this comment

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

The main reason why I think it is better to have one function is that all classes are in the same place. It is easier to work with them, and find issues, like conflicting classes that override each other and mess up styling.

Copy link
Member

Choose a reason for hiding this comment

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

Visual tests are second-class tests, IMO. Unit tests and integration tests are much more important from a development perspective; visual tests are only there to test the aesthetic parts, not behavior.

Complexity is not a problem only about testability, but it is also a problem with readability. We need to think of how other developers will read this code in future, walking through all of these conditions, sometimes you (the original writer) will be the one to re-unveil its secrets.

I agree with Tomas, just moving the complexity from a function to another doesn't solve the problem, it might make the main component function aesthetically "pleasing" but we still need to walk through the complexity if we want to understand how the styling works, now with some extra 20 lines for the function definition. I believe this function is clear, but perhaps we should remove the extra lines for restructuring and leave it as props: Props, and just change the references to props.prop. Can you also add a unit test for it? Tests are also good documentation complements, as they make clear what are our intents in an English text, having it as a separate function is a great step on making the code testable

I believe that adding tests here to be a good opportunity to improve on this component, which will become one of the most complex we have

Tests should be easy and fun, you can just import it and check for all conditionals, example:

import { getInputClassName } from './utils'

describe('getInputClassName', () => {
  describe('when disabled', () => {
    it('styles webkit fill color', () => {
      const result = getInputClassName({ disabled })
      
      expect(result).toContain('[&::-webkit-text-fill-color]:text-gray-600')
    })
    
    // ...
  })
})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see now the benefit of testing these functions. But I'm not sure about props.prop . I see, that we don't use this approach in our components and util functions. It happens from time to time, mostly in tests. So I prefer to leave destructuring. Are you ok with that @augustobmoura, @TomasSlama?

Comment on lines 32 to 33
width === 'shrink' && 'w-auto',
width === 'auto' && 'w-[18.75rem]',
Copy link
Contributor

Choose a reason for hiding this comment

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

even though this looks weird, it is correct, I double checked with master ;-)

Copy link
Member

@augustobmoura augustobmoura left a comment

Choose a reason for hiding this comment

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

Checked the code, but we still have a few differences on Happo that need to be addressed. I believe adding unit tests here will also be a huge improvement for the future, this is a major component on our stack and having it well tested is very important

Comment on lines 248 to 256
return multiline ? (
<Input
{...sharedProps}
multiline={true}
rows={getRows(rows)}
maxRows={getRows(rowsMax)}
/>
) : (
<Input {...sharedProps} />
Copy link
Member

Choose a reason for hiding this comment

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

If the component is the same and we only change multiline, rows, maxRows, perhaps we should only have them conditionally added, while also keeping the props inlined with the element

Suggested change
return multiline ? (
<Input
{...sharedProps}
multiline={true}
rows={getRows(rows)}
maxRows={getRows(rowsMax)}
/>
) : (
<Input {...sharedProps} />
return (
<Input
className='...'
style={{}}
// ...
multiline={multiline ? true : undefined}
rows={multiline ? getRows(rows) : undefined}
maxRows={multiline ? getRows(rowsMax) : undefined}
/>
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will not work like this due to Input props type. It is a union of two different types. And for the one where multiline is true, rows and maxRows should not be undefined. And due to that it should be splited to two different returns. Otherwise, TS will throw an error.

Copy link
Member

@augustobmoura augustobmoura Jun 27, 2024

Choose a reason for hiding this comment

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

Got it. We still can have only the multiline props on an object while the remaining is inlined without the ternary expression, wdyt?
Having most of props on a object instead of inlined on the JSX element is a bit unstylistic and we lose

Suggested change
return multiline ? (
<Input
{...sharedProps}
multiline={true}
rows={getRows(rows)}
maxRows={getRows(rowsMax)}
/>
) : (
<Input {...sharedProps} />
const multilineProps = multiline
? ({
multiline: true,
rows: getRows(rows),
maxRows: getRows(rowsMax),
} as const)
: {}
return (
<Input
className='...'
style={{}}
// ...
{...multilineProps}
/>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is nice 🚀

)

// eslint-disable-next-line complexity
export const getInputClassName = ({
Copy link
Member

Choose a reason for hiding this comment

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

Visual tests are second-class tests, IMO. Unit tests and integration tests are much more important from a development perspective; visual tests are only there to test the aesthetic parts, not behavior.

Complexity is not a problem only about testability, but it is also a problem with readability. We need to think of how other developers will read this code in future, walking through all of these conditions, sometimes you (the original writer) will be the one to re-unveil its secrets.

I agree with Tomas, just moving the complexity from a function to another doesn't solve the problem, it might make the main component function aesthetically "pleasing" but we still need to walk through the complexity if we want to understand how the styling works, now with some extra 20 lines for the function definition. I believe this function is clear, but perhaps we should remove the extra lines for restructuring and leave it as props: Props, and just change the references to props.prop. Can you also add a unit test for it? Tests are also good documentation complements, as they make clear what are our intents in an English text, having it as a separate function is a great step on making the code testable

I believe that adding tests here to be a good opportunity to improve on this component, which will become one of the most complex we have

Tests should be easy and fun, you can just import it and check for all conditionals, example:

import { getInputClassName } from './utils'

describe('getInputClassName', () => {
  describe('when disabled', () => {
    it('styles webkit fill color', () => {
      const result = getInputClassName({ disabled })
      
      expect(result).toContain('[&::-webkit-text-fill-color]:text-gray-600')
    })
    
    // ...
  })
})

@ruslan-sed
Copy link
Contributor Author

@augustobmoura there is a note about those failing tests in the PR description

@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch from 0998017 to d8020fb Compare June 26, 2024 13:34
Copy link
Member

@augustobmoura augustobmoura left a comment

Choose a reason for hiding this comment

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

Left a small comment at #4363 (comment), please resolve it before merging.

GJ on the PR! 👍


import styles from './styles'
import { getInputClassName, getRootClassName, getRows } from './styles/styles'
Copy link
Contributor

Choose a reason for hiding this comment

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

just for consistency, we don't have styles folder in any component, we can just have OutlinedInput/styles.ts

Comment on lines 228 to 240
className: getRootClassName({
size,
width,
type,
layout,
isDark,
multiline,
highlight,
disabled,
className,
classes,
isError,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
className: getRootClassName({
size,
width,
type,
layout,
isDark,
multiline,
highlight,
disabled,
className,
classes,
isError,
}),
className: twMerge(
getRootClassName({
size,
width,
type,
layout,
isDark,
multiline,
highlight,
disabled,
isError,
}),
className,
classes.root
),

'text-gray-600',
// On Safari the text gets a bit lighter as if it had some transparency applied to it
// We need this webkit-specific property to achieve the exact font color
disabled ? '[&::-webkit-text-fill-color]:text-gray-600' : '',
Copy link
Contributor

Choose a reason for hiding this comment

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

this condition will be always true as it is already in if (disabled)

// On Safari the text gets a bit lighter as if it had some transparency applied to it
// We need this webkit-specific property to achieve the exact font color
disabled ? '[&::-webkit-text-fill-color]:text-gray-600' : '',
].join(' ')
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
].join(' ')
]

twJoin and twMerge accepts arrays, so we don't need to join these. It can even work with nested arrays

@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch from d68ae19 to 9d272d9 Compare June 27, 2024 14:29
@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch from 174b904 to 86bf502 Compare June 27, 2024 15:22
@ruslan-sed ruslan-sed force-pushed the fx-4599-migrate-outlineinput branch from 86bf502 to 08b0403 Compare June 27, 2024 15:25
@ruslan-sed
Copy link
Contributor Author

ruslan-sed commented Jun 27, 2024

@TomasSlama

After refactoring visual tests provides the expected result. What is left is to add tests for style utils.

I have a concern about styles.ts file. It became too big. I know that in other components we usually have only one styles.ts, but in this case for readability purposes maybe we should separate the content of our styles.ts into two different files for root and input functions. In this case files will be smaller and simpler to read and work with them.

And in this case, it also can make sense to have them in a separate folder, where we also can have tests for them.

@TomasSlama TomasSlama merged commit 49984f1 into feature/tailwind-w21 Jun 28, 2024
7 checks passed
@TomasSlama TomasSlama deleted the fx-4599-migrate-outlineinput branch June 28, 2024 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants