-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
[Labs] InputList & Select components #1159
Conversation
scaffold tests, disable coverage check in labs packagePreview: documentation |
gulp/util/karma-config.js
Outdated
@@ -3,6 +3,15 @@ | |||
*/ | |||
"use strict"; | |||
|
|||
const COVERAGE_PERCENT = 80; | |||
const COVERAGE_PERCENT_MAX = 90; | |||
const COVERAGE_CHECK = { |
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 only used once, I don't think you need to make a const
|
||
public getInterface = (name: string) => { | ||
return this.props[name]; | ||
// remove generics from end of name | ||
const actualName = /^(\w+)<?/.exec(name)[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.
hmm this feels like it should probably be fixed at the ts-quick-docs layer? interfaces have names and type params, which we could preserve in a data object and avoid this regex
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.
agreed but needed quick fix
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.
filed giladgray/ts-quick-docs#25 to track
gulp/util/karma-config.js
Outdated
@@ -3,6 +3,15 @@ | |||
*/ | |||
"use strict"; | |||
|
|||
const COVERAGE_PERCENT = 80; | |||
const COVERAGE_PERCENT_MAX = 90; |
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.
"max" doesn't sound right semantically; what does istanbul call these numbers?
packages/labs/src/inputList.tsx
Outdated
* and https://github.com/palantir/blueprint/blob/master/PATENTS | ||
*/ | ||
|
||
// import * as classNames from "classnames"; |
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.
commented code
packages/labs/src/inputList.tsx
Outdated
|
||
import { IProps, Keys, Utils } from "@blueprintjs/core"; | ||
|
||
export interface ICoreInputListProps<D> extends IProps { |
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 type param should be T
... D
probably isn't semantically clear enough to warrant a deviation from the naming standard (same for all the other interfaces)
packages/labs/src/inputList.tsx
Outdated
* Customize filtering of entire items array. Return subset of items that pass filter. | ||
* (Some filter algorithms operate on the entire set, rather than individual items.) | ||
*/ | ||
itemsFilterer?: (items: D[], query: string) => D[]; |
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.
w/ above suggestion: itemListPredicate
packages/labs/src/inputList.tsx
Outdated
export class InputList<D> extends React.Component<IInputListProps<D>, IInputListState<D>> { | ||
public static displayName = "Blueprint.InputList"; | ||
|
||
public static ofType<T>() { return InputList as new () => InputList<T>; } |
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 it has a return
keyword might as well make it multiline...
packages/labs/src/inputList.tsx
Outdated
*/ | ||
private shouldCheckActiveItemInViewport: boolean; | ||
|
||
constructor(props?: IInputListProps<D>, context?: any) { |
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.
props isn't optional
packages/labs/src/select.tsx
Outdated
inputProps?: IInputGroupProps & HTMLInputProps; | ||
|
||
/** React props to spread to `Popover`. Note that `content` cannot be changed. */ | ||
popoverProps?: Partial<IPopoverProps>; |
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.
need Partial<IPopoverProps> & object
; currently this type is too permissive
packages/labs/src/select.tsx
Outdated
leftIconName="search" | ||
onChange={onQueryChange} | ||
placeholder="Filter..." | ||
rightElement={<InputClearButton onClick={this.clearQuery} visible={query.length > 0} />} |
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.
meh, this doesn't seem better than inlining the ternary... {query.length > 0 ? <Button .../> : undefined}
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 fits on one line and doesn't require a const
. analogous to renderItems
member below; is that pattern preferable?
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.
yeah, I'd prefer to encapsulate it inside this class as a private method. or just an inline expression in this method. const InputClearButton
is only used once, so it doesn't make much sense as a separate component
packages/labs/README.md
Outdated
Current components: | ||
|
||
- `InputList` is a low-level component for composing an `InputGroup` to filter an array of `items`. | ||
- `Select` is a high-level component for choosing items for a list. It can be used instead of the |
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.
for choosing items from
a list?
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
{ title: "Inglourious Basterds", year: 2009 }, | ||
{ title: "Snatch", year: 2000 }, | ||
{ title: "3 Idiots", year: 2009 }, | ||
{ title: "Monty Python and the Holy Grail", year: 1975 }, |
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.
Would it make sense to select then F5
in VS Code to alphabetize these? Would placate my inner demon.
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 they're actually ordered, top 100 movies from imdb. i should note in code.
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.
renamed const accordingly: TOP_100_FILMS
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.
Sounds good.
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.
(Resolved)
refactor clear button SFCPreview: documentation | table |
ICoreInputListProps => IListItemsProps cuz it’s all about a list of items. Remove `onQueryChange` because it’s a renderer implementation detail. Rename renderer props to `handle*` to indicate they are handler callbacks, not events.
Pull activeItem state up to Select; InputList requires controlled props for it now (and translates item to index).
…repaint Super awesome for scroll adjustments + no flicker!
resolved all issues by @cmslewis above. the z-index weirdness was due to top margin on the input group causing the top 5px of popover to have transparent background, so navbar border would show through it 😱 . i had to wiggle some styles to get the padding to work correctly. |
major docs updates, fix navbar/border bleed issuePreview: documentation | table |
Looking great so far! One minor UX request: can we get ESC to close the dropdown when it's open & focused? |
packages/labs/src/inputList.tsx
Outdated
* Selection handler that should be invoked when a new item has been chosen, | ||
* perhaps because the user clicked it. | ||
*/ | ||
handleItemSelect: (item: T, event: React.SyntheticEvent<HTMLElement>) => void; |
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.
generally, handlers in props interfaces should be named onItemSelect
. is there a reason why this is exempt?
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 not an event listener prop for you to supply but an actual handler function, passed to the renderer
callback prop:
// very pseudocode ahead
<InputList renderer=({ handleItemSelect }) => <div onClick={handleItemSelect} />) />
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.
see the docs note on this interface:
An object with the following properties will be passed to an
InputList
renderer
.
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.
cool ok
packages/labs/src/select.tsx
Outdated
|
||
public state: ISelectState<T> = { isOpen: false, query: "" }; | ||
|
||
private DInputList = InputList.ofType<T>(); |
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.
missed a rename? StaticInputList
maybe? ConcreteInputList
?
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.
oh TInputList
. this is neither static nor concrete.
Can we make that a |
esc key is already supported, and pressing it closes the popover. what is missing? also, |
|
|
{ title: "Inglourious Basterds", year: 2009 }, | ||
{ title: "Snatch", year: 2000 }, | ||
{ title: "3 Idiots", year: 2009 }, | ||
{ title: "Monty Python and the Holy Grail", year: 1975 }, |
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.
(Resolved)
import { Select } from "../src"; | ||
import { Film, TOP_100_FILMS } from "./data"; | ||
|
||
const MovieSelect = Select.ofType<Film>(); |
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.
nit: FilmSelect
for consistency of terminology?
* and https://github.com/palantir/blueprint/blob/master/PATENTS | ||
*/ | ||
|
||
|
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.
nit (lintable?): delete double newline?
if (nextProps.items !== this.props.items | ||
|| nextProps.itemListPredicate !== this.props.itemListPredicate | ||
|| nextProps.itemPredicate !== this.props.itemPredicate | ||
|| nextProps.query !== this.props.query |
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.
Let's use Utils.shallowCompareKeys
eventually, or now if you already have it on this branch.
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.
that function is only in table
right? don't want to make that refactor now...
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.
Good point. 😛
packages/labs/src/inputList.tsx
Outdated
|
||
function getFilteredItems<T>({ items, itemPredicate, itemListPredicate, query }: IInputListProps<T>) { | ||
if (Utils.isFunction(itemListPredicate)) { | ||
// TODO: this can reorder the items, which can cause weirdness while filtering. do some design. |
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.
TODO
in this PR?
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.
these are questions for the future
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.
turns out this one is fine--the reordering is useful and the component handles it well.
packages/labs/src/inputList.tsx
Outdated
/** | ||
* Customize filtering of entire items array. Return subset of items that pass filter. | ||
* (Some filter algorithms operate on the entire set, rather than individual items.) | ||
*/ |
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.
Can we clarify how these two props (itemPredicate and itemListPredicate) work together if they're both defined? In that case, is an error thrown, is one of these props ignored, etc. (I see from the code that itemListPredicate takes precedence.)
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.
there is a section in the docs
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 that it would be nice if we can provide a warning that says something along the lines of "you've provided both itemListPredicate and itemPredicate, itemPredicate will be ignored" or something.
|
||
public render() { | ||
// omit props specific to this component, spread the rest. | ||
// TODO: should InputList just support arbitrary props? could be useful for re-rendering |
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.
TODO
in this PR? if not, can we file an issue and add a link here?
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.
what do you think about the question? should it just support [key: string]: any;
so you can spread arbitrary things?
packages/labs/src/select.tsx
Outdated
private renderItems({ activeItem, filteredItems, handleItemSelect }: IInputListRendererProps<T>) { | ||
const { itemRenderer, noResults } = this.props; | ||
return filteredItems.length > 0 | ||
? filteredItems.map((item) => itemRenderer(item, item === activeItem, (e) => handleItemSelect(item, e))) |
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.
Is it up to the consumer to add a key
to each item? Should we mention that in the docs?
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 can add a note in prop docs. but also React will tell you.
feedback here seems pretty minor; let's get a rough cut of this out and fixup small things later |
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.
looks awesome!
packages/labs/README.md
Outdated
- `InputList` is a low-level component for composing an `InputGroup` to filter an array of `items`. | ||
- `Select` is a high-level component for choosing items from a list. It can be used instead of the | ||
HTML `<select>` element. | ||
- `Suggest` is a high-level component for an `InputGroup` that suggests completions based on its |
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.
Is this not part of this PR?
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.
Not yet, we're building that next
onItemSelect={this.handleValueChange} | ||
popoverProps={{ popoverClassName: minimal ? Classes.MINIMAL : "" }} | ||
> | ||
<Button text={film ? film.title : "(No selection)"} rightIconName="double-caret-vertical" /> |
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.
nit: recommend 1 prop per line if there are > 1 props, for better diffing
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 do it based on line length typically but i hear that
"style": "dist/blueprint-labs.css", | ||
"unpkg": "dist/labs.bundle.js", | ||
"dependencies": { | ||
"@blueprintjs/core": "^1.18.0", |
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.
(not knowing how blueprint dependencies are normally packaged) Would it be better to keep blueprintjs/core as peerDependency so that there aren't version conflicts with applications that already consume blueprint, but at a different version?
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.
nope, peer dependencies are annoying in general and should be avoided. there's no harm to having multiple versions of blueprint in your node_modules
tree, and everyone should be setting semver ranges appropriately so that they rarely get into that situation anyway. since we ship a single global CSS file, it's pretty hard to run into conflicts within a single app (your bundler will only pick up node_modules/@blueprinjs/core/dist/blueprint.css
)
packages/labs/src/inputList.tsx
Outdated
/** | ||
* Customize filtering of entire items array. Return subset of items that pass filter. | ||
* (Some filter algorithms operate on the entire set, rather than individual items.) | ||
*/ |
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 that it would be nice if we can provide a warning that says something along the lines of "you've provided both itemListPredicate and itemPredicate, itemPredicate will be ignored" or something.
packages/labs/src/inputList.tsx
Outdated
* The active item is the current keyboard-focused element. | ||
* Listen to `onActiveItemChange` for updates from interactions. | ||
*/ | ||
activeItem: T; |
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 this be T | undefined
, since if filtered items = [], there would be no active item? same for onActiveItemChange
below
|
||
private getActiveIndex() { | ||
// NOTE: this operation is O(n) so it should be avoided in render(). safe for events though. | ||
return this.state.filteredItems.indexOf(this.props.activeItem); |
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.
nit: this function gets called multiple times every time the user presses up/down and may lead to a laggy experience if the filteredItems size is large and the user holds up/down. May be able to make this faster with something like an ordered map but probably not worth the trouble if there are plans to virtualize this. Actually, if the number of items is large, the rendering perf may dwarf whatever perf issue this may result in...
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.
worth consideration: a simple @debounce
packages/labs/src/select.tsx
Outdated
itemRenderer: (item: T, isActive: boolean, onClick: React.MouseEventHandler<HTMLElement>) => JSX.Element; | ||
|
||
/** React child to render when filtering items returns zero results. */ | ||
noResults?: string | JSX.Element; |
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.
thoughts on changing this to nonIdealState
? I think that this could be used for displaying a spinner if the user decides to use server-side search/pagination on input change
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.
Let's keep it as is for now, some apps have complex empty states that I need to review again to make sure NonIdealState
is always appropriate. (I suppose the current JSX.Element
already allows you to provide your own NonIdealState
👍)
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.
using an actual NonIdealState
in the menu might be too much UI for a small space? i prefer this name, as the semantics are clear but what you put there is up to you.
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.
an actual NonIdealState
is definitely too big for this, but can totally see it being used for a "non ideal state". I think the name noResults
might be a bit limiting but up to you
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.
but this prop is specifically rendered when there are no results: empty items
array or query returns empty array (same thing really).
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.
hmm i suppose you're right
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.
Oh I misunderstood, I thought you were talking about the typing. Up to you guys on the prop naming, we can always change it later
|
||
export interface ISelectState<T> { | ||
activeItem?: T; | ||
isOpen?: boolean; |
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.
isOpen
and query
are never undefined. Should they be optional? or is it the blueprint's style to make all state fields optional?
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.
blueprint style cuz we use old react typings (pre-Pick
)
packages/labs/src/select.tsx
Outdated
// not using defaultProps cuz they're hard to type with generics (can't use <T> on static members) | ||
const { filterable = true, inputProps = {}, popoverProps = {} } = this.props; | ||
const { handleKeyDown, handleKeyUp, query } = listProps; | ||
|
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.
nit: unnecessary newline?
} | ||
} | ||
|
||
public scrollActiveItemIntoView() { |
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.
seems like a good candidate for taking out into a reusable function/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.
i mean it is in a reusable component... InputList
doesn't do much else beyond keyboard selection
Any thoughts about supporting debouncing the list render for when the query changes? re-rendering a lot of items, for every query change can be expensive and can lead to a laggy experience |
packages/labs/src/select.tsx
Outdated
* this item is active (selected by keyboard arrows) and an `onClick` event handler that | ||
* should be attached to the returned element. | ||
*/ | ||
itemRenderer: (item: T, isActive: boolean, onClick: React.MouseEventHandler<HTMLElement>) => JSX.Element; |
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.
thoughts on including the item index as well? Can support use cases like rendering N items
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.
might as well move to object argument like renderer: (props) => JSX.Element
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.
IMO that would work better if you include the index
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.
Looks good for a first pass! We can keep working on things of course. <3 Labs.
fix testsPreview: documentation | table |
Changes proposed in this pull request:
@blueprintjs/labs
package for unstable and in-development componentsInputList
component composes input to filter list of items.Select
usesInputList
to build basic dropdown chooser functionality.Reviewers should focus on:
Screenshot