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

[Mobile] Add external link inline component #33210

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3eafe48
Simply External Link component
enejb Jul 3, 2021
268032b
Update the Anchor Hook to use ExternalLink
enejb Jul 3, 2021
776e27c
Remove FooterMessageLink and replace it with ExternalLink
enejb Jul 3, 2021
f0e6c45
Update external link readme
enejb Jul 7, 2021
a43a43e
Add New HelpText Component.
enejb Jul 7, 2021
81ff9e1
Include the Help Text in components
enejb Jul 7, 2021
ecc59c9
Update BottomSheet text control
enejb Jul 7, 2021
f3edfeb
Add accessibilityRole and accessibilityHint to ExternalLink
enejb Jul 7, 2021
4d2ff4f
Update Image Edit to use HelpText
enejb Jul 7, 2021
9e751d1
Move Help Text
enejb Jul 7, 2021
8e12d48
Update the anchor to use HelpText
enejb Jul 7, 2021
4991880
Fix the url link
enejb Aug 20, 2021
34d57bb
Minor fix
enejb Aug 20, 2021
b9c0c64
Update the ImageEdit to use HelpText
enejb Aug 20, 2021
8727684
Merge branch into trunk
Sep 15, 2022
ade76f0
Adapt code to work with new Typescript component
Sep 15, 2022
70c13f9
Fix lint error: Remove whitespace in string
Sep 15, 2022
ce04ee0
Revert accidental code deletion
Sep 15, 2022
dec04bc
Update reference to old/removed component
Sep 16, 2022
4eba51b
Add necessary space between text and URL
Sep 16, 2022
85f8a01
Add necessary space between text and link
Sep 16, 2022
0c82978
Update README for ExternalLink for accuracy
Sep 16, 2022
d449feb
Only add space if there is *both* text and a URL
Sep 16, 2022
60ade1d
Apply correct style to HelpText component
Sep 16, 2022
8e51b87
Refine native styles to match FooterControl
Sep 16, 2022
5f71efe
Tweak HelpText styling to match FooterControl
Sep 16, 2022
3f092cf
Refactor FooterMessageControl in LinkSettings
Sep 16, 2022
52d62bc
Introduce a top border for the HelpText component
Sep 16, 2022
5a3584e
Update README description for clarity
Nov 2, 2022
5a701e5
Update README description for clarity
Nov 2, 2022
67cd28b
Update README description for clarity
Nov 2, 2022
79ea8b5
Delete unused 'footer-message-link' file
Nov 2, 2022
8ee995c
Help component: Avoid rendering link without text
Nov 2, 2022
8d4d17e
Throw error if wrong data type is passed to link
Nov 2, 2022
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
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,12 @@
"markdown_source": "../packages/components/src/heading/README.md",
"parent": "components"
},
{
"title": "HelpText",
"slug": "help-text",
"markdown_source": "../packages/components/src/help-text/README.md",
"parent": "components"
},
{
"title": "NavigateRegions",
"slug": "navigate-regions",
Expand Down
51 changes: 33 additions & 18 deletions packages/block-editor/src/hooks/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { PanelBody, TextControl, ExternalLink } from '@wordpress/components';
import {
PanelBody,
TextControl,
HelpText,
ExternalLink,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { hasBlockSupport } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
Expand Down Expand Up @@ -70,23 +75,6 @@ export const withInspectorControl = createHigherOrderComponent(
<TextControl
className="html-anchor-control"
label={ __( 'HTML anchor' ) }
help={
<>
{ __(
'Enter a word or two — without spaces — to make a unique web address just for this block, called an “anchor.” Then, you’ll be able to link directly to this section of your page.'
) }

{ isWeb && (
<ExternalLink
href={ __(
'https://wordpress.org/support/article/page-jumps/'
) }
>
{ __( 'Learn more about anchors' ) }
</ExternalLink>
) }
</>
}
value={ props.attributes.anchor || '' }
placeholder={ ! isWeb ? __( 'Add an anchor' ) : null }
onChange={ ( nextValue ) => {
Expand All @@ -97,6 +85,23 @@ export const withInspectorControl = createHigherOrderComponent(
} }
autoCapitalize="none"
autoComplete="off"
separatorType="none"
help={
isWeb && (
<>
{ __(
'Enter a word or two — without spaces — to make a unique web address just for this block, called an “anchor.” Then, you’ll be able to link directly to this section of your page.'
) }
<ExternalLink
href={
'https://wordpress.org/support/article/page-jumps/'
}
>
{ __( 'Learn more about anchors' ) }
</ExternalLink>
</>
)
Comment on lines +90 to +103
Copy link
Member

Choose a reason for hiding this comment

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

🤔 This comment is quite broad and provides more questions than solutions. I understand if we'd prefer to move forward without addressing this.


Here the web now relies upon passing help to a robust, composable TextControlBaseControl component that manages help text and links; native now relies upon HelpText composed further along in this hook file rather than the previous approach of passing help down to TextControlCell.

While native's Cell is quite complex and overly far-reaching — it is quite different from the web's composable approach in BaseControl and likely not the best abstraction long term — I am hesitant move the help text from TextControlCell to inline HelpText in this hook as it further diverges web and native component design, which is unfortunate.

My interpretation for the rationale of this change is that we cannot pass a multiple children types — string and interactive link — on native. Doing so leads to the Object Object accessibility (a11y) label issue. My hope is that we could identify a way to address the a11y label issue while also avoiding the divergence from web's help property here.

An example of a possible approach might involve flattening help props rather than introducing the HelpText component. The flattened help prop could be used for a11y labels, the original help prop could be rendered as normal for visible UI. Note: I didn't test any performance of such code.

const helpPropExample = (
  <>
    A top-level sentence.{" "}
    <ExternalLink href={"https://wordpress.org/support/article/page-jumps/"}>
      A nested link
    </ExternalLink>
  </>
);

function flattenElementToString(reactElement = "") {
  if (!reactElement) {
    return;
  }
  const children = reactElement.props?.children || reactElement;

  if (Array.isArray(children)) {
    return children.reduce(
      (acc, child) => acc + flattenElementsToString(child),
      ""
    );
  } else if (React.isValidElement(children)) {
    return flattenElementsToString(children);
  }

  return children;
}

const flatHelpPropExample = flattenElementToString(helpPropExample);
console.log(">>>", flatHelpPropExample); // >>> A top-level sentence. A nested link

<Text accessibilityLabel={flatHelpPropExample}>{helpPropExample}</Text>;

}
/>
);

Expand All @@ -118,6 +123,16 @@ export const withInspectorControl = createHigherOrderComponent(
<InspectorControls>
<PanelBody title={ __( 'Heading settings' ) }>
{ textControl }
<HelpText
url="https://wordpress.org/support/article/page-jumps/"
moreLinkText={ __(
'Learn more about anchors'
) }
>
{ __(
'Enter a word or two — without spaces — to make a unique web address just for this block, called an “anchor.” Then, you’ll be able to link directly to this section of your page.'
) }
</HelpText>
</PanelBody>
</InspectorControls>
) }
Expand Down
17 changes: 4 additions & 13 deletions packages/block-library/src/embed/embed-link-settings.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
LinkSettingsNavigation,
FooterMessageLink,
} from '@wordpress/components';
import { LinkSettingsNavigation } from '@wordpress/components';
import { isURL } from '@wordpress/url';
import { useDispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
Expand Down Expand Up @@ -35,15 +32,9 @@ const EmbedLinkSettings = ( {
autoFocus,
autoFill: true,
},
footer: {
label: (
<FooterMessageLink
href={ __(
'https://wordpress.org/support/article/embeds/'
) }
value={ __( 'Learn more about embeds' ) }
/>
),
help: {
url: __( 'https://wordpress.org/support/article/embeds/' ),
moreLinkText: __( 'Learn more about embeds' ),
Comment on lines -38 to +37
Copy link
Member

@dcalhoun dcalhoun Oct 20, 2022

Choose a reason for hiding this comment

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

🤔 This comment is quite broad and provides more questions than solutions. I understand if we'd prefer to move forward without addressing this.


The replacement of the composable approach with a prop-driven approach is less than ideal IMO. I.e. the ability to pass React elements means we have more optionality in regards to what we pass without defining additional props. The prop-driven approach limits us to a very specific composition defined within HelpText.

For this specific example of help, this ultimate uses HelpText, but only passes props for a "more link." So, in reality, this only needs to render an ExternalLink, not an entire HelpText. This unnecessary complexity is less than ideal IMO.

My understanding for the rationale for this change is similar to my previous comment, it is difficult to pass both a string and a interactive link and not run into accessibility label issues.

separatorType: 'topFullWidth',
},
};
Expand Down
31 changes: 13 additions & 18 deletions packages/block-library/src/image/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ import {
BottomSheet,
BottomSheetTextControl,
BottomSheetSelectControl,
FooterMessageControl,
FooterMessageLink,
HelpText,
Badge,
} from '@wordpress/components';
import {
Expand Down Expand Up @@ -574,18 +573,17 @@ export class ImageEdit extends Component {
placeholder={ __( 'Add alt text' ) }
label={ __( 'Alt Text' ) }
icon={ textColor }
footerNote={
<>
help={
<HelpText
url={
'https://www.w3.org/WAI/tutorials/images/decision-tree/'
}
moreLinkText={ __( 'What is alt text?' ) }
>
{ __(
'Describe the purpose of the image. Leave empty if the image is purely decorative.'
) }{ ' ' }
<FooterMessageLink
href={
'https://www.w3.org/WAI/tutorials/images/decision-tree/'
}
value={ __( 'What is alt text?' ) }
/>
</>
) }
</HelpText>
}
/>
);
Expand Down Expand Up @@ -747,14 +745,11 @@ export class ImageEdit extends Component {
>
{ canImageBeFeatured &&
this.getFeaturedButtonPanel( isFeaturedImage ) }
<FooterMessageControl
label={ __(
<HelpText>
{ __(
'Changes to featured image will not be affected by the undo/redo buttons.'
) }
cellContainerStyle={
styles.setFeaturedButtonCellContainer
}
/>
</HelpText>
</PanelBody>
</InspectorControls>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/social-link/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const linkSettingsOptions = {
label: __( 'Link label' ),
placeholder: __( 'None' ),
},
footer: {
help: {
label: __( 'Briefly describe the link to help screen reader user' ),
},
};
Expand Down
25 changes: 18 additions & 7 deletions packages/components/src/external-link/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@
* External dependencies
*/

import { TouchableOpacity, Text, Linking } from 'react-native';
import { Text, Linking } from 'react-native';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { external, Icon } from '@wordpress/icons';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
/**
* Internal dependencies
*/
import style from './style.native.scss';

export function ExternalLink( { href, children } ) {
const externalLink = usePreferredColorSchemeStyle(
style[ 'external-link' ],
style[ 'external-link__dark' ]
);

return (
<TouchableOpacity
<Text
style={ externalLink }
onPress={ () => Linking.openURL( href ) }
accessibilityLabel={ __( 'Open link in a browser' ) }
accessibilityLabel={ children }
Copy link
Member

Choose a reason for hiding this comment

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

To avoid future a11y label issues, it may be worth throwing an error in this component if typeof children !== 'string'. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed that's a good idea, I've added an error message in 8d4d17e.

accessibilityHint={ __( 'Opens link in a browser' ) }
accessibilityRole={ 'link' }
>
<Text>{ children }</Text>
<Icon icon={ external } />
</TouchableOpacity>
{ children }
</Text>
);
}

Expand Down
7 changes: 7 additions & 0 deletions packages/components/src/external-link/style.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.external-link {
color: $blue-50;
}

.external-link__dark {
color: $blue-30;
}
31 changes: 31 additions & 0 deletions packages/components/src/help-text/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# HelpText

Renders a help text shell with a ExternalLink appended at the end.
SiobhyB marked this conversation as resolved.
Show resolved Hide resolved

## Usage

```jsx
import { HelpText } from '@wordpress/components';

const HelpfulText = () => (
<HelpText url="https://wordpress.org" moreLinkText="Learn More">WordPress is an easy to use publishing tool</HelpText>
);
```

## Props

The text that's displayed within the component's tags will be "clickable" and extenal link will be added at the end.
Copy link
Member

Choose a reason for hiding this comment

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

This statement feels out of place underneath the heading of "Props." Was this intended to be a description of the children prop?

Also, I believe this only to be true on native, as it currently applies an onPress to a wrapping Text element. The web version merely renders the child text. I wonder if we can better describe this.


### `url`

An optional URL that is being linked to and will open in a new window when tapped or clicked.
SiobhyB marked this conversation as resolved.
Show resolved Hide resolved

- Type: `String`
- Required: No

### `moreLinkText`

An optional Text as the click abel text at the end.
SiobhyB marked this conversation as resolved.
Show resolved Hide resolved

- Type: `String`
- Required: No
24 changes: 24 additions & 0 deletions packages/components/src/help-text/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Internal dependencies
*/
import { ExternalLink } from '../external-link';

/**
* @param {Object} props
* @param {string} props.moreLinkText
* @param {import('react').ReactNode} props.children
* @param {string} props.url
*/
const HelpText = ( { moreLinkText, children, url } ) => {
Copy link
Member

@dcalhoun dcalhoun Oct 20, 2022

Choose a reason for hiding this comment

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

🤔 This comment is quite broad and provides more questions than solutions. I understand if we'd prefer to move forward without addressing this.


This is the first time I have encountered us adding a web component. That may be OK, but it is a bit odd to add it when there is no usage of it anywhere.

For the most part, I've seen the web project lead, and the native project sometimes splits existing web components. Additionally, there is a packages/components/src/mobile/ directory housing mobile-only components for specific mobile UI patterns that, I believe, arrived during the mono-repo merge.

I raise this primarily as it is largely uncharted territory for me. WDYT?

return (
<div>
{ children }
{ children && url && ' ' }
{ url && (
<ExternalLink href={ url }>{ moreLinkText }</ExternalLink>
) }
</div>
);
};

export default HelpText;
61 changes: 61 additions & 0 deletions packages/components/src/help-text/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* External dependencies
*/
import { View, Text, AccessibilityInfo, Linking } from 'react-native';

/**
* WordPress dependencies
*/
import { ExternalLink } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';

/**
* Internal dependencies
*/
import styles from './styles.scss';

const HelpText = ( { moreLinkText, children, url, separatorType } ) => {
if ( typeof children === 'string' ) {
children = (
<Text style={ styles[ 'help-text__text' ] }>{ children }</Text>
);
}

const separatorStyle = usePreferredColorSchemeStyle(
styles[ 'help-text__separator' ],
styles[ 'help-text__separator--dark' ]
);

return (
<>
{ separatorType === 'topFullWidth' && (
<View style={ separatorStyle } />
) }
<View style={ styles[ 'help-text' ] }>
<Text
accessibilityRole={ url ? 'link' : 'text' }
accessibilityHint={
url ? __( 'Opens link in a browser' ) : null
}
Comment on lines +39 to +42
Copy link
Member

Choose a reason for hiding this comment

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

This combination of props along with the children text appear to work for iOS. However, it does not appear to read any label loud for Android.

From testing briefly, a quick fix is adding accessibilityLabel={ moreLinkText }. It causes the moreLinkText to be read for both platforms. Unfortunately, it no longer reads the entire help text aloud on iOS.

Using something like accessibilityLabel={ children + moreLinkText } is not possible as it reads aloud "Object Object," presumably because of translation string methods used: __().

onPress={ () => {
AccessibilityInfo.isScreenReaderEnabled().then(
( enabled ) => enabled && Linking.openURL( url )
);
} }
Comment on lines +43 to +47
Copy link
Member

Choose a reason for hiding this comment

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

Similar to accessibilityRole and accessibilityHint, I wonder if this should be conditionally set if there is a url defined.

onPress={ url ? () => { /* [...] */ } : undefined }

style={ styles[ 'help-text__text' ] }
>
{ children }
{ children && url && ' ' }
{ url && (
Copy link
Member

Choose a reason for hiding this comment

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

To avoid the possibility of a link without text, I wonder if we should create a compound conditional for all checks of url in this file. E.g.:

const hasMoreLink = url && moreLinkText
// [...]
{ children && hasMoreLink && ' ' }
{ hasMoreLink && ( /* [...] */ ) }

Copy link
Contributor

Choose a reason for hiding this comment

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

I appreciate that thought, gone ahead to implement your suggestion in 8ee995c.

<ExternalLink href={ url }>
{ moreLinkText }
</ExternalLink>
) }
</Text>
</View>
</>
);
};

export default HelpText;
20 changes: 20 additions & 0 deletions packages/components/src/help-text/styles.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.help-text {
flex-direction: row;
min-height: 48;
align-items: center;
justify-content: space-between;
}

.help-text__text {
font-size: 12px;
color: $gray;
}

.help-text__separator {
background-color: $light-gray-400;
height: 1px;
}

.help-text__separator--dark {
background-color: $gray-70;
}
7 changes: 7 additions & 0 deletions packages/components/src/help-text/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.help-text {
font-size: 12px;
}

.help-text__text {
color: $gray;
}
1 change: 1 addition & 0 deletions packages/components/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export { Grid as __experimentalGrid } from './grid';
export { default as Guide } from './guide';
export { default as GuidePage } from './guide/page';
export { Heading as __experimentalHeading } from './heading';
export { default as HelpText } from './help-text';
export { HStack as __experimentalHStack } from './h-stack';
export { default as Icon } from './icon';
export { default as IconButton } from './button/deprecated';
Expand Down
Loading