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

components: Promote VisuallyHidden from ui into full components #31244

Merged
merged 5 commits into from
Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Basic rendering should render 1`] = `"<div tabindex=\\"-1\\" class=\\"block-editor-link-control\\"><div class=\\"block-editor-link-control__search-input-wrapper\\"><form><div class=\\"components-base-control block-editor-url-input block-editor-link-control__search-input css-wdf2ti-Wrapper e1puf3u0\\"><div class=\\"components-base-control__field css-11vcxb9-StyledField e1puf3u1\\"><input required=\\"\\" class=\\"block-editor-url-input__input\\" type=\\"text\\" placeholder=\\"Search or type url\\" role=\\"combobox\\" aria-label=\\"URL\\" aria-expanded=\\"false\\" aria-autocomplete=\\"list\\" aria-owns=\\"block-editor-url-input-suggestions-0\\" value=\\"\\"></div></div><div class=\\"block-editor-link-control__search-actions\\"><button type=\\"submit\\" class=\\"components-button block-editor-link-control__search-submit has-icon\\" aria-label=\\"Submit\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M6.734 16.106l2.176-2.38-1.093-1.028-3.846 4.158 3.846 4.157 1.093-1.027-2.176-2.38h2.811c1.125 0 2.25.03 3.374 0 1.428-.001 3.362-.25 4.963-1.277 1.66-1.065 2.868-2.906 2.868-5.859 0-2.479-1.327-4.896-3.65-5.93-1.82-.813-3.044-.8-4.806-.788l-.567.002v1.5c.184 0 .368 0 .553-.002 1.82-.007 2.704-.014 4.21.657 1.854.827 2.76 2.657 2.76 4.561 0 2.472-.973 3.824-2.178 4.596-1.258.807-2.864 1.04-4.163 1.04h-.02c-1.115.03-2.229 0-3.344 0H6.734z\\"></path></svg></button></div></form></div><fieldset class=\\"block-editor-link-control__settings\\"><legend class=\\"components-visually-hidden\\">Currently selected link settings</legend><div class=\\"components-base-control components-toggle-control block-editor-link-control__setting css-wdf2ti-Wrapper e1puf3u0\\"><div class=\\"components-base-control__field css-11vcxb9-StyledField e1puf3u1\\"><span class=\\"components-form-toggle\\"><input class=\\"components-form-toggle__input\\" id=\\"inspector-toggle-control-0\\" type=\\"checkbox\\"><span class=\\"components-form-toggle__track\\"></span><span class=\\"components-form-toggle__thumb\\"></span></span><label for=\\"inspector-toggle-control-0\\" class=\\"components-toggle-control__label\\">Open in new tab</label></div></div></fieldset></div>"`;
exports[`Basic rendering should render 1`] = `"<div tabindex=\\"-1\\" class=\\"block-editor-link-control\\"><div class=\\"block-editor-link-control__search-input-wrapper\\"><form><div class=\\"components-base-control block-editor-url-input block-editor-link-control__search-input css-wdf2ti-Wrapper e1puf3u0\\"><div class=\\"components-base-control__field css-11vcxb9-StyledField e1puf3u1\\"><input required=\\"\\" class=\\"block-editor-url-input__input\\" type=\\"text\\" placeholder=\\"Search or type url\\" role=\\"combobox\\" aria-label=\\"URL\\" aria-expanded=\\"false\\" aria-autocomplete=\\"list\\" aria-owns=\\"block-editor-url-input-suggestions-0\\" value=\\"\\"></div></div><div class=\\"block-editor-link-control__search-actions\\"><button type=\\"submit\\" class=\\"components-button block-editor-link-control__search-submit has-icon\\" aria-label=\\"Submit\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M6.734 16.106l2.176-2.38-1.093-1.028-3.846 4.158 3.846 4.157 1.093-1.027-2.176-2.38h2.811c1.125 0 2.25.03 3.374 0 1.428-.001 3.362-.25 4.963-1.277 1.66-1.065 2.868-2.906 2.868-5.859 0-2.479-1.327-4.896-3.65-5.93-1.82-.813-3.044-.8-4.806-.788l-.567.002v1.5c.184 0 .368 0 .553-.002 1.82-.007 2.704-.014 4.21.657 1.854.827 2.76 2.657 2.76 4.561 0 2.472-.973 3.824-2.178 4.596-1.258.807-2.864 1.04-4.163 1.04h-.02c-1.115.03-2.229 0-3.344 0H6.734z\\"></path></svg></button></div></form></div><fieldset class=\\"block-editor-link-control__settings\\"><legend class=\\"components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0\\">Currently selected link settings</legend><div class=\\"components-base-control components-toggle-control block-editor-link-control__setting css-wdf2ti-Wrapper e1puf3u0\\"><div class=\\"components-base-control__field css-11vcxb9-StyledField e1puf3u1\\"><span class=\\"components-form-toggle\\"><input class=\\"components-form-toggle__input\\" id=\\"inspector-toggle-control-0\\" type=\\"checkbox\\"><span class=\\"components-form-toggle__track\\"></span><span class=\\"components-form-toggle__thumb\\"></span></span><label for=\\"inspector-toggle-control-0\\" class=\\"components-toggle-control__label\\">Open in new tab</label></div></div></fieldset></div>"`;
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Snapshot Diff:
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="color-picker-saturation-4"
+ id="color-picker-saturation-3"
>
Expand Down Expand Up @@ -86,7 +86,7 @@ Snapshot Diff:
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="components-color-picker__hue-description-4"
+ id="components-color-picker__hue-description-3"
>
Expand Down Expand Up @@ -161,7 +161,7 @@ Snapshot Diff:
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="color-picker-saturation-8"
+ id="color-picker-saturation-7"
>
Expand Down Expand Up @@ -208,7 +208,7 @@ Snapshot Diff:
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="components-color-picker__hue-description-8"
+ id="components-color-picker__hue-description-7"
>
Expand Down Expand Up @@ -283,7 +283,7 @@ Snapshot Diff:
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="color-picker-saturation-10"
+ id="color-picker-saturation-9"
>
Expand Down Expand Up @@ -330,7 +330,7 @@ Snapshot Diff:
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="components-color-picker__hue-description-10"
+ id="components-color-picker__hue-description-9"
>
Expand Down Expand Up @@ -405,7 +405,7 @@ Snapshot Diff:
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="color-picker-saturation-6"
+ id="color-picker-saturation-5"
>
Expand Down Expand Up @@ -452,7 +452,7 @@ Snapshot Diff:
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="components-color-picker__hue-description-6"
+ id="components-color-picker__hue-description-5"
>
Expand Down Expand Up @@ -511,7 +511,7 @@ Snapshot Diff:
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="color-picker-saturation-2"
+ id="color-picker-saturation-1"
>
Expand All @@ -537,7 +537,7 @@ Snapshot Diff:
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden css-1r162ic-VisuallyHidden css-1mm2cvy-View egi4jkx0"
- id="components-color-picker__hue-description-2"
+ id="components-color-picker__hue-description-1"
>
Expand Down Expand Up @@ -574,20 +574,56 @@ Snapshot Diff:
`;

exports[`ColorPicker should render color picker 1`] = `
.emotion-4 {
.emotion-0 {
border: 0;
-webkit-clip: rect( 1px,1px,1px,1px );
clip: rect( 1px,1px,1px,1px );
-webkit-clip-path: inset( 50% );
-webkit-clip-path: inset( 50% );
clip-path: inset( 50% );
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
word-wrap: normal !important;
}

.emotion-0:focus {
background-color: #edeff0;
-webkit-clip: auto !important;
clip: auto !important;
-webkit-clip-path: none;
clip-path: none;
color: #444;
display: block;
font-size: 1em;
height: auto;
left: 5px;
line-height: normal;
padding: 15px 23px 14px;
-webkit-text-decoration: none;
text-decoration: none;
top: 5px;
width: auto;
z-index: 100000;
}

.emotion-10 {
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',sans-serif;
font-size: 13px;
}

.emotion-2 {
.emotion-8 {
margin-bottom: 8px;
}

.components-panel__row .emotion-2 {
.components-panel__row .emotion-8 {
margin-bottom: inherit;
}

.emotion-0 {
.emotion-6 {
display: inline-block;
margin-bottom: 8px;
}
Expand Down Expand Up @@ -631,7 +667,7 @@ exports[`ColorPicker should render color picker 1`] = `
type="button"
/>
<div
className="components-visually-hidden"
className="components-visually-hidden emotion-0 emotion-1 emotion-2"
id="color-picker-saturation-0"
>
Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation.
Expand Down Expand Up @@ -691,7 +727,7 @@ exports[`ColorPicker should render color picker 1`] = `
tabIndex="0"
/>
<p
className="components-visually-hidden"
className="components-visually-hidden emotion-0 emotion-1 emotion-2"
id="components-color-picker__hue-description-0"
>
Move the arrow left or right to change hue.
Expand All @@ -708,13 +744,13 @@ exports[`ColorPicker should render color picker 1`] = `
className="components-color-picker__inputs-fields"
>
<div
className="components-base-control components-color-picker__inputs-field emotion-4 emotion-5"
className="components-base-control components-color-picker__inputs-field emotion-10 emotion-11"
>
<div
className="components-base-control__field emotion-2 emotion-3"
className="components-base-control__field emotion-8 emotion-9"
>
<label
className="components-base-control__label emotion-0 emotion-1"
className="components-base-control__label emotion-6 emotion-7"
htmlFor="inspector-text-control-0"
>
Color value in hexadecimal
Expand Down
1 change: 0 additions & 1 deletion packages/components/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,3 @@
@import "./toolbar-button/style.scss";
@import "./toolbar-group/style.scss";
@import "./tooltip/style.scss";
@import "./visually-hidden/style.scss";
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function FormGroupLabel( { children, id, labelHidden = false, ...props } ) {

if ( labelHidden ) {
return (
<VisuallyHidden as="label" htmlFor={ id }>
<VisuallyHidden as="label" htmlFor={ id?.toString() }>
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious why this change was needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The previous prop types were not comprehensive so they didn't have strict checking of htmlFor. But id comes in as either a number | string and htmlFor, when properly type checked against a label only accepts strings.

The polymorphism that existed in the previous component implementation didn't actually make any difference to the types, but now that it's a true PolymorphicComponent that has ViewOwnProps for its arguments, it actually gets the correct full prop types when using the as prop.

Copy link
Contributor

Choose a reason for hiding this comment

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

Question though, it's just for typing right? I mean, a user passing a numeric value there will still work? (thinking about BC)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup! It'll still work, just a type issue.

{ children }
</VisuallyHidden>
);
Expand Down
1 change: 0 additions & 1 deletion packages/components/src/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ export * from './text';
export * from './truncate';
export * from './v-stack';
export * from './view';
export * from './visually-hidden';
17 changes: 0 additions & 17 deletions packages/components/src/ui/visually-hidden/README.md

This file was deleted.

2 changes: 0 additions & 2 deletions packages/components/src/ui/visually-hidden/index.js

This file was deleted.

42 changes: 0 additions & 42 deletions packages/components/src/ui/visually-hidden/stories/index.js

This file was deleted.

14 changes: 11 additions & 3 deletions packages/components/src/visually-hidden/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# VisuallyHidden

A component used to render text intended to be visually hidden, but will show for alternate devices, for example a screen reader.
`VisuallyHidden` is a component used to render text intended to be visually hidden, but will show for alternate devices, for example a screen reader.

### Usage
## Usage

```jsx
<VisuallyHidden> Show text for screenreader. </VisuallyHidden>
import { VisuallyHidden } from '@wordpress/components';

function Example() {
return (
<VisuallyHidden>
<label>Code is Poetry</label>
</VisuallyHidden>
);
}
```
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import { createComponent } from '../utils';
import { createComponent } from '../ui/utils';
import { useVisuallyHidden } from './hook';

/**
Expand All @@ -10,12 +10,12 @@ import { useVisuallyHidden } from './hook';
*
* @example
* ```jsx
* import { View, VisuallyHidden } from `@wordpress/components/ui`;
* import { VisuallyHidden } from `@wordpress/components`;
*
* function Example() {
* return (
* <VisuallyHidden>
* <View as="label">Code is Poetry</View>
* <label>Code is Poetry</label>
* </VisuallyHidden>
* );
* }
Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed this createComponent util. Looking at its code, it seems it doesn't do much and basically coping that component here would make things simpler for me at least.

What do you think about it? Is it a good abstraction to have?

Copy link
Contributor

Choose a reason for hiding this comment

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

For instance why useVisuallyHidden is a hook in the first place, it just adds a class, so why not just add it inline?

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 think it's a good abstraction to have and encourages "pure hook" components, which I think is generally a good thing (makes us think twice before using Context or adding extra components to the React tree for example).

Additionally it's used for a ton of components in ui and refactoring them all to just copy the code from createComponent would increase the maintenance cost of all those components.

Copy link
Contributor

Choose a reason for hiding this comment

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

makes us think twice before using Context or adding extra components to the React tree for example

Why is this a bad thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Additionally, it enforces the use of View where we will be able to add back the base styles that we lost by moving away from the custom style system. Because everything is ultimately a View we can just apply the base styles there.

Copy link
Contributor

Choose a reason for hiding this comment

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

Now looking more at createCompnent, It does call memo by default in all components, that personally doesn't make sense to me. It's going to do the opposite of what it's meant to solve. Basically, most of these small components don't need to be memoized, React already only update the underlying Dom element if its prop changes, so this extra memo is useless. At least we should change the default value to false there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My main worry is that it's an abstraction that doesn't bring much while raising the bar for casual components to understand how components are built in this package, why it's just just a regular component.

That's fair, it is a new abstraction and does technically redirect from the core functionality of a component.

How do you envision, for example, the Text component looking if we removed createComponent? Would we get rid of the useText hook?

My concern is that if we remove createComponent, we'll just be duplicating a ton of code everywhere, indeed, that's why Reakit uses it for example. If a component is follows the pattern established in createComponent, namely:

  1. Push all props through a hook
  2. Pass all processed props to View
  3. Connect to the contextSystem

Then we've just duplicated that code, well, more than duplicated, we've replicated probably dozens of times over across the components codebase, just to avoid a tidy, well-tested abstraction. Is that worth it? 🤷‍♀️

Copy link
Contributor

Choose a reason for hiding this comment

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

How do you envision, for example, the Text component looking if we removed createComponent? Would we get rid of the useText hook?

Well! for me these useVisuallyHidden, useText... hooks are very weird hooks. They don't really match what hooks are meant to be used for. They correspond to "components" and not "behavior". It looks like a hack to the hooks system :P

Push all props through a hook

What is this exactly? :P Is it a requirement to pass all props through a hook?

Push all props through a hook

Do all components need to support "as" and other features proposed by the "View" component? I guess my opinion is probably "no" Why should an Icon component support that? an Icon always renders an svg (just an example)

Connect to the contextSystem

Do all components need to connect to the context system. Isn't that just useless overhead for most components? Not sure I understand that system well enough but it seems only useful for components that compose (Card, CardBody...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At least we should change the default value to false there.

Agreed! We need to update that in contextConnect as well.

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 looks like a hack to the hooks system :P

That's fair, they probably are 😅

Is it a requirement to pass all props through a hook?

No, but it's just the way that the g2 components were designed from a technical perspective, to encapsulate all, or as much as possible, logic into a hook that can be reused by other components.

For example, if another component wanted to implement the same logic as useText without rendering a Text element (like useHeading for example, does everything a Text can do but with some extras, same with VStack, it's just HStack with some extra props, which in turn is just Flex configured a specific way), this is now possible using these "hacky" hooks.

I'm not saying it's not complicated, but I think approach the technical design of components in this way does add some value around composibility within the library itself (without necessarily exposing all the details of that to consumers of the library).

Do all components need to support "as" and other features proposed by the "View" component

I think so. It's hard to decide what components don't benefit from the as prop. It's an extremely powerful tool for composition.

Icon itself probably doesn't need it, you're correct, but I think the exceptions are few.

Do all components need to connect to the context system

You'd be surprised. We even have examples of Text being accessed via the context system.

I have wondered about the increased overhead of the context system but I think ultimately it's negligible. There were other things like memoization that apparently yielded more significant performance benefits: ItsJonQ/g2#57

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ import { cx } from 'emotion';
*/
import * as styles from './styles';

// duplicate this for the sake of being able to export it, it'll be removed when we replace VisuallyHidden in components/src anyway
/** @typedef {import('../context').ViewOwnProps<{}, 'div'>} Props */

/**
* @param {import('../context').ViewOwnProps<{}, 'div'>} props
* @param {import('../ui/context').ViewOwnProps<{}, 'div'>} props
*/
export function useVisuallyHidden( { className, ...props } ) {
// circumvent the context system and write the classnames ourselves
Copy link
Contributor

Choose a reason for hiding this comment

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

should we remove the wp-components-visually-hidden classname?

const classes = cx(
'components-visually-hidden wp-components-visually-hidden',
'components-visually-hidden',
className,
styles.VisuallyHidden
);
Expand Down
48 changes: 2 additions & 46 deletions packages/components/src/visually-hidden/index.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,2 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import { renderAsRenderProps } from './utils';
import { withNextComponent } from './next';

/**
* @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T
* @typedef OwnProps
* @property {T} [as='div'] Component to render, e.g. `"div"` or `MyComponent`.
*/

/**
* @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T
* @typedef {OwnProps<T> & import('react').ComponentPropsWithRef<T>} Props
*/

/**
* VisuallyHidden component to render text out non-visually
* for use in devices such as a screen reader.
*
* @template {keyof JSX.IntrinsicElements | import('react').JSXElementConstructor<any>} T T
* @param {Props<T>} props
* @param {import('react').Ref<any>} forwardedRef
* @return {JSX.Element} Element
*/
function VisuallyHidden( { as = 'div', className, ...props }, forwardedRef ) {
return renderAsRenderProps( {
as,
className: classnames( 'components-visually-hidden', className ),
...props,
ref: forwardedRef,
} );
}

export default withNextComponent( forwardRef( VisuallyHidden ) );
export { default } from './component';
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we export the component twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some places were importing it as the default and others were importing it by name, so I figured this would minimize the number of changes elsewhere in the components package. I can switch to just one or the other if you prefer though (probably just the named export is best?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we historically started with default exports for components. Even if I personally would prefer named components now, I think it's probably better to stay consistent and use default export here.

export * from './hook';
Loading