-
Notifications
You must be signed in to change notification settings - Fork 2
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
refactor: #136 TextArea - Add CSS resizing #150
Conversation
a81340c
to
a1ece07
Compare
@@ -77,7 +77,7 @@ export const BasicForm = { | |||
</InputWrap> | |||
<InputWrapFull> | |||
<InputGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: type
is no longer an accepted prop for TextArea, so fixing up these stories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, agreed, there's no need
@@ -2,7 +2,7 @@ import { styled } from '@linaria/react' | |||
import { ElIcon } from '../../icon/__styles__' | |||
import { ElLabel } from '../../label/__styles__' | |||
import { ElInput, elHasInputError } from '../../input/__styles__' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Fixing up to import from the root textarea
folder instead of relying on internal file structures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Moved to textarea/styles.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: The current approach of snapshotting the render
result is not ideal. It means we are capturing a host of React Testing Library's implementation details which are irrelevant to our component unit tests. As you'll see in the unit test file, I've updated the unit tests to snapshot the document fragment that contains our rendered component.
aside: It may be helpful to add this as a task to all the v5 refactor issues so that devs can remember to update the tests.
@@ -1,9 +1,19 @@ | |||
import { render } from '@testing-library/react' | |||
import { render, screen } from '@testing-library/react' | |||
import { TextArea } from '..' | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I've updated the unit tests to avoid the generic "should match a snapshot". While this kind of unit test provides some value, it's missing out (IMO) on one of the most important aspects of unit tests: documentation. As such, I've replaced it with three tests that cover some of the important aspects we would want to document about our TextArea, though one is currently skipped (see code comment).
aside: I would not say that these unit tests are hitting the bar of "necessary and sufficient" by themselves, but the addition of the visual snapshots helps get us pretty close as they provide coverage of the invalid, disabled and read-only states, plus the initial sizing of text areas based on the minRows
, maxRows
and rows
props.
I can provide more info on what the terms "necessary and sufficient" mean separately (they are terms we recently introduced to the Console FE team).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have some docs on this, would be more than happy to add to the wiki
src/components/textarea/styles.ts
Outdated
'--textarea-min-rows': number | ||
} | ||
|
||
export const ElTextArea = styled.textarea` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: The code comment below highlights an important decision I made in the implementation of this component. Instead of relying on Linaria's ability to convert ${({ maxRows }) => ...}
into a randomly named CSS variable for us, I've opted to handle it manually in order to provide well-named CSS variables that CSS-only consumers can leverage.
I would have preferred to rely on data attributes on the textarea, but there's currently no way for CSS to use the value of a data attribute. See attr() for more information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, makes sense - appreciate the comments in here too
// a friendly interface for CSS-only consumers. | ||
--textarea-max-rows: infinity; | ||
--textarea-min-rows: 2; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: The code comment below is another important decision. While I strongly believe there should be zero developer-defined, component-specific design tokens in Elements, I don't think that should prevent us from leveraging CSS variables in a "private" manner to aide the definition of our styles.
In order to try and communicate they are "private"/"implementation detail" CSS variables, I've prefixed them with the standard double underscore, __
. Not sure if that's something we want to do or not.
If we'd prefer not to do this at all, I can remove these and just use the real design tokens directly in all the places they're referenced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does look a bit odd syntactically but the comment explains fine. I would suggest as long as they are outside the root scope it's probably fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@willmcvay I agree the syntax looks weird. We could choose to use private
as a prefix? i.e. --private-textarea-border-width
🤔
a1ece07
to
c7fdf7f
Compare
@supports (field-sizing: content) { | ||
field-sizing: content; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: The code comment below highlights an important design decision I've made with the max and min height constraints for the text area. Since we want CSS-only consumers to get the correct resizing behaviour, we need to facilitate the max/min constraints in CSS, not JS.
I've used the min and max block-size
CSS properties as they account for the consumer's writing-mode.
c7fdf7f
to
8db308a
Compare
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode | ||
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/max-block-size | ||
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/min-block-size | ||
max-block-size: calc( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: We use the new lh
unit to calculate the length of the rows, then add on the padding and border. See MDN's docs on CSS values and units for more information on lh
, but the TL;DR is:
lh
andrlh
are relative lengths units similar toem
andrem
. The difference betweenlh
andrlh
is that the first one is relative to the line height of the element itself, while the second one is relative to the line height of the root element, usually<html>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did just check this out in Safari just because Safari and couldn't see any issues FYI
outline: none; | ||
border-color: var(--outline-text_input-focus); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: the original Elements text area did not have styles for disabled or read-only states. These have been added here. The Design System does not currently differentiate visually between disable and read-only; this may change in future.
&:disabled { | ||
background-color: var(--fill-default-lightest); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: The original text area only applied the invalid state styles when the elTextAreaHasError
class was present. This has been expanded to include the :invalid
and :user-invalid
pseudo-classes.
src/components/textarea/textarea.mdx
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Some minor updates here to include the extra stories that have been added.
aside: I'm keen to understand why Elements prefers MDX files over Storybook's autogenerated DocsPage functionality. What I'm doing here (e.g. the use of the Description
component to render the JS Doc comments from the component or a specific story) is all handled by Storybook's DocsPage feature, so I'm not sure what value we are getting from doing this ourselves 🤔 Is it primarily to facilitate the RenderHtmlMarkup
component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I've expanded the number of stories being demonstrated here, as well as applied Storybook's types to the exports. The JS Doc for each story is picked up by Storybook's Description
component, which is now in use in textarea.mdx
.
src/components/textarea/textarea.tsx
Outdated
|
||
// NOTE: We omit the `cols` prop because our text area should always grow to the width of its container. | ||
interface RestrictedTextareaHTMLAttributes extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'cols'> {} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I've updated how the component's prop interface is defined for two reasons:
- To add the new
minRows
andmaxRows
props; and, - To use the
TextareaHTMLAttributes
instead ofInputHTMLAttributes
(the latter one is for<input>
elements).
* resizing behaviour is available for CSS-only consumers on Chrome and Edge. For browsers that do not yet | ||
* support the [field-sizing](https://developer.mozilla.org/en-US/docs/Web/CSS/field-sizing) property, we | ||
* fallback to a JS-based resizing solution that is only available to React-based consumers. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I've opted to use forwardRef's generic type slots instead of the original TextAreaWrapped
type. This makes for a simpler approach and ensures our forwarded ref is correctly typed.
src/components/textarea/textarea.tsx
Outdated
// NOTE: `rows` takes precedence. If it's defined, the text area is effectively fixed-height. | ||
// If it's not defined, we allow resizing to occur between the min and max rows. | ||
'--textarea-max-rows': rows ?? maxRows, | ||
'--textarea-min-rows': rows ?? minRows, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I'm using satisfies TextAreaCSSProperties
(a type which comes from our styles.ts
module) in order to ensure we don't provide more styles here than what we explicitly permit through the TextAreaCSSProperties
.
I then assert this to be the CSSProperties
type in order to satisfy React/Linaria's expectations (they don't consider the CSS variables to be valid property names).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @kurtdoherty so looks good to me - have tested locally in Safari, FF & Chromium because of some of the edgier properties and works as expected which is great - will check out the JS fallback next.
Comments are appreciated for the more non-vanilla implementations - I appreciate, it's a tricky bit of code.
Just that small issue with the stories file to move to the new structure in SB but happy if you want to move that to the second PR.
@@ -77,7 +77,7 @@ export const BasicForm = { | |||
</InputWrap> | |||
<InputWrapFull> | |||
<InputGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, agreed, there's no need
"rerender": [Function], | ||
"unmount": [Function], | ||
} | ||
exports[`can set custom min/max rows 1`] = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much cleaner without the React internals :-)
@@ -1,9 +1,19 @@ | |||
import { render } from '@testing-library/react' | |||
import { render, screen } from '@testing-library/react' | |||
import { TextArea } from '..' | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have some docs on this, would be more than happy to add to the wiki
src/components/textarea/styles.ts
Outdated
'--textarea-min-rows': number | ||
} | ||
|
||
export const ElTextArea = styled.textarea` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, makes sense - appreciate the comments in here too
// a friendly interface for CSS-only consumers. | ||
--textarea-max-rows: infinity; | ||
--textarea-min-rows: 2; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does look a bit odd syntactically but the comment explains fine. I would suggest as long as they are outside the root scope it's probably fine.
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode | ||
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/max-block-size | ||
// @see https://developer.mozilla.org/en-US/docs/Web/CSS/min-block-size | ||
max-block-size: calc( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did just check this out in Safari just because Safari and couldn't see any issues FYI
With the other PR this is quite an update on a normal TextArea and if its got bespoke 'tricky' code it might better to make a new component. MaterialUI has a TextareaAutosize component, name makes clear what you are getting https://mui.com/base-ui/react-textarea-autosize/ |
@adrian-reapit I did consider that option originally, but decided to take the approach I've presented in these PRs because:
|
@kurtdoherty @adrian-reapit Just to resolve the slight log-jam, why not take the "Styles Only" usage approach and export a wrapped version of |
@willmcvay we already are exporting the basic styled component, I'm not sure there's much value in exporting another component that simply wraps it? The atom would want to be called TextArea, but that's the name of the "main" component too. This isn't a problem with the Accordion atoms because they're all uniquely named 🤔 |
note: I have a meeting with Andrei today to talk through the component interface, particularly around what design would like to see as the properties for their TextArea in Figma and how that works for us, so there may be some minor changes land after that conversation. |
Will leave up to you. IMO, I am in favour of the implementation you presented - was just a suggestion to alleviate any concerns Adrian had. Will leave you to guys to agree a preference. If the auto-resizing behaviour is what the design team want, I am very much on board with delivering this. |
question: @adrian-reapit just checking in to see if your concerns about the approach taken in this PR and #151 are "blocking", or if you're okay for me to proceed? If they're blocking, I'd like to line up a video call for us to chat through our options as soon as possible so we can get this work finished off and merged in 🙏 |
From @adrian-reapit via a Teams chat:
I appreciate the concern around the adoption/migration cost for v4 consumers. Console Cloud has a significant migration ahead of it too. A few thoughts though:
|
844f2d4
to
509ae02
Compare
509ae02
to
2f8e95e
Compare
From @adrian-reapit via Teams chat:
Done 👍 I've introduced the new |
Context
This PR (Part 1)
__styles__/index.ts
tostyles.ts
per the project structure conventions;ElTextArea
styled component to:elTextAreaHasError
class name is applied, or the:invalid
or:user-invalid
pseudo-classes are active;:disabled
and:read-only
states;ElTextArea
to leverage experimental field-sizing property if supported;note: The design guidelines have not be finalised for this component, so there may be some additional changes once they are, particularly the treatment of default min/max rows values or the TextArea being resizable by default as opposed to fixed size by default.
note: This PR is on the large size, so if you'd like me to split it up further I can. In hindsight, the Storybook updates could have been left until later 😅