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

[Control] add labelElement prop #1378

Merged
merged 4 commits into from
Jul 28, 2017
Merged

[Control] add labelElement prop #1378

merged 4 commits into from
Jul 28, 2017

Conversation

llorca
Copy link
Contributor

@llorca llorca commented Jul 24, 2017

Fixes #1351

A new prop must be added for TypeScript support because the existing label prop in React.HTMLAttributes only accepts string values, so tsc rejects JSX elements.

JS consumers can happily pass JSX to the label prop (cuz no compiler to stop them and the code does actually support JSX) but we need to add a new prop to sidestep the typings for TS consumers.

@llorca llorca requested review from cmslewis and giladgray July 24, 2017 21:14
@blueprint-bot
Copy link

Type label with ReactNode

Preview: documentation
Coverage: core | datetime

cmslewis
cmslewis previously approved these changes Jul 24, 2017
Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

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

Just checked into the typings for ReactNode to make sure strings, numbers, etc., were still supported. Looks like we're good!

//
// React Nodes
// http://facebook.github.io/react/docs/glossary.html
// ----------------------------------------------------------------------

type ReactText = string | number;
type ReactChild = ReactElement<any> | ReactText;

// Should be Array<ReactNode> but type aliases cannot be recursive
type ReactFragment = {} | Array<ReactChild | any[] | boolean>;
type ReactNode = ReactChild | ReactFragment | boolean;

@giladgray
Copy link
Contributor

giladgray commented Jul 25, 2017

❌ THIS DOES NOT COMPILE. Try actually using it in JSX:

<Checkbox label={<strong>"Gilad Gray"</strong>} defaultIndeterminate />

and you'll see an error:

file: 'file:///Volumes/git/blueprint/packages/core/examples/controlsExample.tsx'
severity: 'Error'
message: 'Type 'Element' is not assignable to type 'string | (string & number) | (string & true) | (string & false) | (string & {}) | (string & React...'.
  Type 'Element' is not assignable to type 'string & (string | number | boolean | any[] | ReactElement<any>)[]'.
    Type 'Element' is not assignable to type 'string'.'
at: '26,27'
source: 'ts'

this is because HTMLAttributes defines label: string | number which does not include JSX.Element. you cannot simply add it to the types because the prop is defined twice, so all types must be compatible.

edit: notice how each type in parens above is (string & X): this means that it really only accepts strings, despite how you've changed the local type here.

@llorca please add labelElement instead, as we discussed previously.

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

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

❌ see comment

@giladgray giladgray changed the title [Control] Type label with ReactNode [Control] add labelElement prop Jul 26, 2017
giladgray
giladgray previously approved these changes Jul 26, 2017
@giladgray giladgray dismissed stale reviews from cmslewis and themself July 26, 2017 00:06

i'm the author now

@blueprint-bot
Copy link

add example to each Control docs section

Preview: documentation
Coverage: core | datetime

@@ -17,8 +17,11 @@ import * as React from "react";
import * as Classes from "../../common/classes";
import { IProps, removeNonHTMLProps } from "../../common/props";
import { safeInvoke } from "../../common/utils";
import { HTMLInputProps } from "../../index";

export interface IControlProps extends IProps, HTMLInputProps {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🎉

];

/** Base Component class for all Controls */
export class Control<P extends IControlProps> extends React.Component<React.HTMLProps<HTMLInputElement> & P, {}> {
export class Control<P extends IControlProps> extends React.Component<P, {}> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we still need <P extends IControlProps> if we already have interfaces extending IControlProps, such as export interface ICheckboxProps extends IControlProps?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes it enforces that the P is a subclass of IControlProps, so I can't pass IDialogProps, for instance. these two extends are separate things.

@@ -62,6 +79,7 @@ export class Control<P extends IControlProps> extends React.Component<React.HTML
<input {...inputProps} ref={inputRef} type={type} />
<span className={Classes.CONTROL_INDICATOR} />
{this.props.label}
{this.props.labelElement}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If both label and labelElement props are provided, do we still render both?

Copy link
Contributor

Choose a reason for hiding this comment

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

what does the code say?

yes, and label comes first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@giladgray Apologies, I meant to ask if we actually want to render both. Sounds error-prone to allow both at the same time

Copy link
Contributor

Choose a reason for hiding this comment

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

why? it's just JSX children, there's no limit. seems more error-prone to only render one.

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 just can't imagine a case where setting those two props at the same time would be intentional. Probably a false concern 🤷‍♂️

Copy link
Contributor

@giladgray giladgray Jul 28, 2017

Choose a reason for hiding this comment

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

nor can i, but doing the simple thing works reasonably well in all cases here 🍰

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

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

love it

Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

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

Yesss.

<Switch labelElement={<strong>Enabled</strong>} />
<Switch labelElement={<em>Public</em>} />
<Switch labelElement={<u>Cooperative</u>} defaultChecked />
<small>This example uses <code>labelElement</code> to demonstrate JSX labels.</small>
Copy link
Contributor

Choose a reason for hiding this comment

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

👏

@llorca llorca merged commit 016f82b into master Jul 28, 2017
@llorca llorca deleted the al/control-1351 branch July 28, 2017 21:08
@thasner
Copy link
Contributor

thasner commented Aug 10, 2017

curious, why add labelElement instead of extending the typings for label?

@llorca
Copy link
Contributor Author

llorca commented Aug 10, 2017

@thasner scroll up and read the convo, especially this comment: #1378 (comment)

@thasner
Copy link
Contributor

thasner commented Aug 10, 2017

👍

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.

5 participants