-
Notifications
You must be signed in to change notification settings - Fork 842
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
Initial commit of typescript type definitions for EUI components & services #252
Conversation
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.
This is a great start 👍 There are some aspects which we might want to make a conscious decision about though:
component type definitions
There is an alternative to declaring classes with empty bodies. Instead one could write
export const EuiButton: React.SFC<EuiButtonProps>;
The differences I can see are the following:
- it does not define a new type, it just assigns a type to the export
- it does not suggest that the component as a state of type
{}
- it does not tempt the author to put something into the class body, but instead restricts him to declaring the prop types
I have not arrived at a conclusion about which style is better. The const
style appeals to me due to its simplicity though.
inline unions
In some places the prop types are defined and exported separately (e.g. ButtonColor
) and in some places they are defined inline in the props interface. Do we want to stick to one style or set some rule about which style to apply when?
common props
There are some props like className
, aria-label
or data-test-subject
, which most components have to support. So we might want to deduplicate them and mix them into the prop type definition using intersection types:
interface CommonProps {
className?: string;
'aria-label'?: string;
'data-test-subj'?: string;
}
interface AnyProps {
[prop: string]: any;
}
interface EuiButtonProps {
/* ... */
}
const EuiButton: React.SFC<
CommonProps &
AnyProps &
EuiButtonProps
>;
pass-through props
If we know that the "rest" of the props are passed through to a specific child component, we could reflect that in the type to avoid unchecked props and missing autocompletion:
export const EuiButton: React.SFC<
CommonProps &
AnyProps &
React.ButtonHTMLAttributes<HTMLButtonElement> &
EuiButtonProps
>;
I wan't aware of
I prefer exporting props separately (it's more open for reuse and extensions)
++ to both |
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.
Another idea we might want to consider is avoiding the repitition of the ambient module definition in every .d.ts
file. Instead using normal export chain in those files should work too (at least it did when I last tested):
// in src/components/badge/index.d.ts
export const EuiBadge: React.SFC<EuiBadgeProps>;
// in src/components/index.d.ts
export * from './components/badge';
// in src/index.d.ts
export * from './components';
@weltenwort updated based on your proposals. I tried to keep it as close as possible to the props that are supported in the
strange... I've never seen this sort of import working within type definitions. I'm inclined to keep using the triple slash directive as this (as far as I know) is the official way you should direct the compiler to other type definitions. Aside from that, personally I don't mind the repetition of the module declaration - the nice side effect is that when you jump to it and you look at the file, you immediately know the module it belongs to. |
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 had a closer look at the code and checked it with the compiler. Aside from the individual comments below a few aspects apply to multiple files:
-
The compiler complains about imports inside the module augmentations, which can be easily fixed by moving them to the outer scope.
-
How about using
React.HTMLAttributes
instead ofReact.DOMAttributes
? They provide a more complete set of properties. -
Do we maybe want to apply
prettier
to the files (similar to the logging and new platform codebase)?
I can prepare a PR to this PR with those changes if you prefer.
src/components/button/index.d.ts
Outdated
|
||
declare module '@elastic/eui' { | ||
|
||
import { SFC, ButtonHTMLAttributes, MouseEventHandler } from 'react'; |
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.
This shows an error for me (imports in module augmentations not allowed). How about moving this to line 3?
src/components/button/index.d.ts
Outdated
|
||
export interface EuiButtonProps { | ||
onClick: MouseEventHandler<HTMLButtonElement>; //overriding DOMAttributes to make this required | ||
iconType?: IconType, |
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.
A /// <reference path="../icon/index.d.ts" />
at the top is required for this to resolve correctly.
src/components/button/index.d.ts
Outdated
CommonProps, | ||
ButtonHTMLAttributes<HTMLButtonElement>, | ||
EuiButtonEmptyProps | ||
>; |
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.
You probably meant to use &
instead of ,
here.
|
||
export type EuiContextMenuPanel = SFC< | ||
CommonProps & | ||
Omit<DOMAttributes<HTMLDivElement>, 'ref', 'onKeyDown', 'tabIndex', 'onAnimationEnd'> & |
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.
How about we use HTMLAttributes
a more complete interface?
Apart from that, the second argument to Omit
should be a union type, i.e. 'ref' | 'onKeyDown' | 'tabIndex' | 'onAnimationEnd'
.
|
||
export type EuiContextMenuItem = SFC< | ||
CommonProps & | ||
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'type', 'ref'> & |
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.
Same as above (|
instead of ,
) and the ButtonHTMLAttributes
don't include ref
in the first place.
src/components/pagination/index.d.ts
Outdated
|
||
export type EuiPaginationButton = SFC< | ||
CommonProps & | ||
Omit<EuiButtonEmptyProps, 'size', 'color'> & |
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.
a union type is required here as well
src/components/panel/index.d.ts
Outdated
|
||
export type EuiPanel = SFC< | ||
CommonProps & | ||
Omit<DOMAttributes<HTMLDivElement>, 'ref'> & |
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.
ref
is not included in DOMAttributes
(or HTMLAttributes
)
src/components/popover/index.d.ts
Outdated
ownFocus?: boolean, | ||
anchorPosition?: PopoverAnchorPosition, | ||
panelClassName?: string, | ||
panelPaddingSize?: PanelPaddingSize, |
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.
This requires a
/// <reference path="../panel/index.d.ts" />
at the top.
src/components/table/index.d.ts
Outdated
*/ | ||
|
||
export interface EuiTableHeaderButtonProps { | ||
iconType?: IconType |
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.
This requires
/// <reference path="../icon/index.d.ts" />
src/components/table/index.d.ts
Outdated
export type EuiTableRowCellCheckbox = SFC< | ||
CommonProps & | ||
TdHTMLAttributes<HTMLTableCellElement> & | ||
EuiTableRowCellCheckbox |
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.
This should probably mean EuiTableRowCellCheckboxProps
.
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 just noticed that the components are only exported as types, but not values. Instead of the export type EuiButton = SFC<...>
we need export const EuiButton: SFC<...>
so they are actually defined in the value scope.
good catch... I guess that was implicit with the class exports before... will fix that |
@weltenwort updated |
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.
Just a few more points I ran into while trying to use these definitions.
src/components/popover/index.d.ts
Outdated
export const EuiPopover: SFC< | ||
CommonProps & | ||
HTMLAttributes<HTMLDivElement> & | ||
EuiPanelProps |
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.
This should probably be EuiPopoverProps
.
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.
👍
export type EuiContextMenuPanelId = string | number; | ||
|
||
export interface EuiContextMenuProps { | ||
panels?: EuiContextMenuPanel[], |
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 ran into problems when trying to use this. Turns out the panels data structure here is not the React component, but an array of objects describing the panels. I wrote a working definition for the KUI context menu a while back, which should still be compatible:
interface ContextMenuPanelItem
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
name: string;
icon?: React.ReactNode;
panel?: string | number;
}
interface ContextMenuPanel {
id: string | number;
title: string;
items?: ContextMenuPanelItem[];
content?: React.ReactNode;
}
export const KuiContextMenu: React.SFC<
CommonProps &
AnyProps & {
initialPanelId: string | number;
panels?: ContextMenuPanel[];
}
>;
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.
pfff... yea.. can you check these ones:
export type EuiContextMenuPanelId = string | number;
interface EuiContextMenuPanelItem extends EuiContextMenuItemProps {
name: string;
panel?: EuiContextMenuPanelId;
}
interface EuiContextMenuPanelDescriptor {
id: EuiContextMenuPanelId,
title: string;
items?: EuiContextMenuPanelItem[];
content?: React.ReactNode;
}
export interface EuiContextMenuProps {
panels?: EuiContextMenuPanelDescriptor[],
initialPanelId?: EuiContextMenuPanelId
}
export const EuiContextMenu: SFC<
Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style'> &
EuiContextMenuProps
>;
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.
Yes, that seems to work. Although I don't think it is correct for EuiContextMenuPanelItem
(which could be called EuiContextMenuItemDescriptor
to clarify the symmetry) to extend EuiContextMenuItemProps
. It should not contain the hasPanel
and onClick
properties, for example (see
eui/src/components/context_menu/context_menu.js
Lines 170 to 198 in 84dadc5
const { | |
panel, | |
name, | |
icon, | |
onClick, | |
...rest | |
} = item; | |
const onClickHandler = panel | |
? () => { | |
// This component is commonly wrapped in a EuiOutsideClickDetector, which means we'll | |
// need to wait for that logic to complete before re-rendering the DOM via showPanel. | |
window.requestAnimationFrame(() => { | |
if (onClick) onClick(); | |
this.showNextPanel(index); | |
}); | |
} : onClick; | |
return ( | |
<EuiContextMenuItem | |
key={name} | |
icon={icon} | |
onClick={onClickHandler} | |
hasPanel={Boolean(panel)} | |
{...rest} | |
> | |
{name} | |
</EuiContextMenuItem> | |
); |
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.
no fully accurate... the onClick
is actually an optional prop on the descriptor as well (just like it's optional on the item itself). The only difference here is the hasPanel
vs. panel. The
...rest` are passthrough props to the item itself as well. I think extending it makes sense to some degree, with perhaps a small modification:
export type EuiContextMenuPanelItemDescriptor = Omit<EuiContextMenuItemProps, 'hasPanel'> & {
name: string;
panel?: EuiContextMenuPanelId;
};
wdyt?
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.
true... I guess what threw me off with the onClick
is the fact that
if (onClick) onClick(); |
(event: MouseEvent<T>) => void
to () => void
. But that's a bug with the original js file. I'll create a PR for that.
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.
The PR is #265.
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 think this is almost ready. Only found a few things remain (see comments below). I couldn't test every prop of every component, so I expect some detail adjustments might be necessary when the definitions are used more comprehensively.
Finally, how do we want to handle the formatting? I would be in favor of applying prettier
to all definition files to remove any ambiguity.
* @see './context_menu_panel.js` | ||
*/ | ||
|
||
import { HTMLAttributes } from 'react'; |
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.
This still throws an error for me. Could probably be removed because line 3 already contains it.
src/components/button/index.d.ts
Outdated
export type ButtonSize = 's' | 'l'; | ||
|
||
export interface EuiButtonProps { | ||
onClick: MouseEventHandler<HTMLButtonElement>; //overriding DOMAttributes to make this required |
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.
Should the comment refer to ButtonHTMLAttributes
?
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's actually defined in DOMAttributes
src/components/button/index.d.ts
Outdated
export type ButtonIconColor = 'primary' | 'danger' | 'disabled' | 'ghost' | 'text'; | ||
|
||
export interface EuiButtonIconProps { | ||
onClick: MouseEventHandler<HTMLButtonElement>; //overriding DOMAttributes to make this required |
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.
Should the comment refer to ButtonHTMLAttributes
?
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.
same here
@weltenwort updated... left the comments as they are (they seem to be very logical to me) |
What do you think about the formatting I proposed? |
I think it makes sense to keep all our TS code consistent across the projects.... unfortunately, I think I'll have to ask you to do that though as my knowledge of build tools and such converges to zero. We can also do it in a separate PR |
I updated this PR with the new formatting. |
- introduced `CommonProps` - introduced `AnyProps` - introduced `NoArgCallback<T>` - introduced `RefCallback<T>` - introduced utility types `Diff` and `Omit` (TS's counterpart for `Pick`)
- changed all component exports to value exports - changed to use HTMLAttributes instead of DOMAttributes - added missing reference directives - moved all imports to sit outside of the module declaration - fixed use of `,` instead of `|` and `&`
- fixed the context menu definitions to better reflect the props contract
- fixed misplaced import
relates to #256