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

[Labs] Timezone Picker #1568

Merged
merged 83 commits into from
Sep 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
356743c
Remove non-existent tsconfig include dir
Sep 12, 2017
66c6edc
Install moment and moment-timezone
Sep 12, 2017
4a4aef2
Remove unused import
Sep 12, 2017
b6d8302
Shorten imports
Sep 12, 2017
456303a
Timezone Input V0
Sep 12, 2017
7de36a1
Use Select component
Sep 12, 2017
67ca676
Seed list with representative timezones
Sep 13, 2017
dad05d1
Additional timezone metadata
Sep 13, 2017
75dd866
Specify general tab size and scss-specific tab size in workspace sett…
Sep 13, 2017
a1ee5cb
Left-aligned time zone offset label
Sep 13, 2017
5739478
Labels look better on right
Sep 13, 2017
f61607c
Enforce a min-width, so the menu doesn't jump around while filtering
Sep 13, 2017
185a671
Move abbreviation after name, so offsets align
Sep 13, 2017
4bb8e12
Additional props: disabled, defaultTimezone, selectedTimezoneFormat
Sep 13, 2017
50f8e78
Clean up naming and initial timezone getter
Sep 13, 2017
0b0237c
showUserTimezoneGuess prop
Sep 13, 2017
25176ab
Pass through popoverProps
Sep 13, 2017
c08169f
placeholder prop
Sep 13, 2017
3e4a482
targetClassName prop
Sep 13, 2017
8823063
Add disabled switch to example; Use handler helpers
Sep 13, 2017
4c2def9
Add interface to docs page
Sep 13, 2017
e5dc667
Add example switches for default timezone and use guess
Sep 13, 2017
ba10a57
Timezone query candidates
Sep 13, 2017
86ade6b
Change empty state text
Sep 13, 2017
cbf7ee3
Only show user timezone if query is empty
Sep 13, 2017
72c143a
Controlled mode for selected timezone
Sep 13, 2017
cd0f2ad
Don't exclude popular guess
Sep 14, 2017
8671cc0
Add onQueryChange to account for the query being changed through reset
Sep 14, 2017
d0f1e4f
Use onQueryChange
Sep 14, 2017
f6f8b93
Add defaultToUserTimezoneGuess prop
Sep 14, 2017
c7b76d2
Make timezone input look more like an input
Sep 14, 2017
1946977
Make placeholder consistent with display format
Sep 14, 2017
b359c1a
Show the timezone input example in the context of date and time pickers
Sep 14, 2017
020d02f
More documentation
Sep 14, 2017
0cc560c
Clean up props; Add documentation
Sep 14, 2017
44e70d1
timezone input -> timezone select
Sep 14, 2017
4d55db2
Custom target renderer
Sep 14, 2017
f237bdc
Handle empty date
Sep 14, 2017
1054cea
Merge remote-tracking branch 'origin/master' into bb/timezone
Sep 14, 2017
f6858a7
Only use text cursor if not disabled
Sep 14, 2017
9400904
Basic and extended examples
Sep 14, 2017
d2f7f78
Placeholder style
Sep 14, 2017
e10b3d3
Fix lint
Sep 14, 2017
2114c6a
Improve documentation
Sep 15, 2017
d9d00bc
Improve props docs
Sep 15, 2017
f652ffe
Optional icon name
Sep 15, 2017
dc85ca6
Don't use the `||` pattern
Sep 15, 2017
b586639
Use custom class name last
Sep 15, 2017
add9e49
Fix nits
Sep 15, 2017
900c07e
Reorder documentation
Sep 15, 2017
bbf0ff1
Rename TimezoneSelect to TimezonePicker
Sep 15, 2017
7fc4720
Remove the display tag from the example
Sep 15, 2017
8782648
Remove confusing default value
Sep 15, 2017
15947e6
Add buttonProps; Better placeholder
Sep 15, 2017
5afc0d7
Remove pt-timezone-picker-target-placeholder
Sep 15, 2017
ef932e9
Remove defaultToLocalTimezone prop
Sep 15, 2017
cd20736
Split timezone utilities into separate files
Sep 15, 2017
b23c37c
Expose input props; More descriptive input placeholder
Sep 15, 2017
4c8cb85
Don't make example button primary
Sep 15, 2017
4ca31e4
Use radio group for formats in example
Sep 15, 2017
7b96c8a
Composite display format
Sep 15, 2017
bf15ba5
Clean up timezone querying
Sep 15, 2017
11631b8
Basic query match ranking
Sep 16, 2017
76cad64
Prioritize exact matches
Sep 16, 2017
bd9ef19
Add fuzzaldrin-plus
Sep 19, 2017
b2b4788
Use fuzzaldrin-plus for timezone item sorting and filtering
Sep 19, 2017
ff49db9
Remove unneeded query candidates because fuzzaldrin is that good
Sep 19, 2017
d427502
Use filter key constant for clarity
Sep 19, 2017
ab8d5d5
Docs nits
Sep 19, 2017
b8e6c32
Merge remote-tracking branch 'origin/master' into bb/timezone
Sep 19, 2017
68ce37c
Make timezone code "prettier"
Sep 19, 2017
7f5df58
Fix lints
Sep 19, 2017
b0be6b6
[Labs/Select] Sync input props value with query value
Sep 19, 2017
ffbb9d3
[Labs/TimezonePicker] Sync input props value with query value
Sep 19, 2017
956b612
Fix misnamed test
Sep 19, 2017
76fccc2
Timezone picker tests
Sep 19, 2017
c208968
Fix nits
Sep 21, 2017
56bf58e
Use module augmentation instead of casting for zone.population
Sep 21, 2017
d5ab205
Don't import `from ".."`
Sep 21, 2017
3453ce0
Fix doc nits
Sep 21, 2017
b14a7c2
Make test style more consistent
Sep 21, 2017
248f670
Fix tests
Sep 21, 2017
2b37613
Concatenate item query candidates for better ranking
Sep 21, 2017
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
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
"**/dist": true,
"**/coverage": true,
"docs": true
},
"editor.insertSpaces": true,
"editor.tabSize": 4,
"[scss]": {
"editor.tabSize": 2
}
}
4 changes: 2 additions & 2 deletions packages/core/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export * from "./position";
export * from "./props";
export * from "./tetherUtils";

import * as classes from "../common/classes";
import * as keys from "../common/keys";
import * as classes from "./classes";
import * as keys from "./keys";
import * as utils from "./utils";

export const Classes = classes;
Expand Down
9 changes: 3 additions & 6 deletions packages/core/src/components/menu/menuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,9 @@ export class MenuItem extends AbstractComponent<IMenuItemProps, IMenuItemState>
if (hasSubmenu) {
const measureSubmenu = this.props.useSmartPositioning ? this.measureSubmenu : null;
const submenuElement = <Menu ref={measureSubmenu}>{this.renderChildren()}</Menu>;
const popoverClasses = classNames(
Classes.MINIMAL,
Classes.MENU_SUBMENU,
popoverProps.popoverClassName,
{ [Classes.ALIGN_LEFT]: this.state.alignLeft },
);
const popoverClasses = classNames(Classes.MINIMAL, Classes.MENU_SUBMENU, popoverProps.popoverClassName, {
[Classes.ALIGN_LEFT]: this.state.alignLeft,
});

content = (
<Popover
Expand Down
8 changes: 7 additions & 1 deletion packages/core/test/menu/menuTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ describe("MenuItem", () => {
);
assert.strictEqual(wrapper.find(Popover).prop("inline"), popoverProps.inline);
assert.strictEqual(wrapper.find(Popover).prop("interactionKind"), popoverProps.interactionKind);
assert.notStrictEqual(wrapper.find(Popover).prop("popoverClassName").indexOf(popoverProps.popoverClassName), 0);
assert.notStrictEqual(
wrapper
.find(Popover)
.prop("popoverClassName")
.indexOf(popoverProps.popoverClassName),
0,
);
assert.notStrictEqual(wrapper.find(Popover).prop("content"), popoverProps.content);
});

Expand Down
1 change: 1 addition & 0 deletions packages/labs/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from "./selectExample";
export * from "./suggestExample";
export * from "./tagInputExample";
export * from "./tooltip2Example";
export * from "./timezonePickerExample";
2 changes: 1 addition & 1 deletion packages/labs/examples/suggestExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import * as classNames from "classnames";
import * as React from "react";

import { Button, Classes, MenuItem, Switch } from "@blueprintjs/core";
import { Classes, MenuItem, Switch } from "@blueprintjs/core";
import { BaseExample } from "@blueprintjs/docs";
import { ISelectItemRendererProps, Suggest } from "../src";
import { Film, TOP_100_FILMS } from "./data";
Expand Down
94 changes: 94 additions & 0 deletions packages/labs/examples/timezonePickerExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
* Licensed under the BSD-3 License as modified (the “License”); you may obtain a copy
* of the license at https://github.com/palantir/blueprint/blob/master/LICENSE
* and https://github.com/palantir/blueprint/blob/master/PATENTS
*/

import * as React from "react";

import { Radio, RadioGroup, Switch } from "@blueprintjs/core";
import { BaseExample, handleBooleanChange, handleStringChange } from "@blueprintjs/docs";

import { TimezoneDisplayFormat, TimezonePicker } from "../src";

export interface ITimezonePickerExampleState {
date?: Date;
disabled?: boolean;
showLocalTimezone?: boolean;
targetDisplayFormat?: TimezoneDisplayFormat;
timezone?: string;
}

export class TimezonePickerExample extends BaseExample<ITimezonePickerExampleState> {
public state: ITimezonePickerExampleState = {
date: new Date(),
disabled: false,
showLocalTimezone: true,
targetDisplayFormat: TimezoneDisplayFormat.OFFSET,
timezone: "",
};

private handleDisabledChange = handleBooleanChange(disabled => this.setState({ disabled }));
private handleShowLocalTimezoneChange = handleBooleanChange(showLocalTimezone =>
this.setState({ showLocalTimezone }),
);
private handleFormatChange = handleStringChange((targetDisplayFormat: TimezoneDisplayFormat) =>
this.setState({ targetDisplayFormat }),
);

protected renderExample() {
const { date, timezone, targetDisplayFormat, disabled, showLocalTimezone } = this.state;

return (
<TimezonePicker
date={date}
value={timezone}
onChange={this.handleTimezoneChange}
valueDisplayFormat={targetDisplayFormat}
showLocalTimezone={showLocalTimezone}
disabled={disabled}
/>
);
}

protected renderOptions() {
return [
[
<Switch
checked={this.state.showLocalTimezone}
label="Show local timezone in initial list"
key="show-local-timezone"
onChange={this.handleShowLocalTimezoneChange}
/>,
<Switch
checked={this.state.disabled}
label="Disabled"
key="disabled"
onChange={this.handleDisabledChange}
/>,
],
[this.renderDisplayFormatOption()],
];
}

private renderDisplayFormatOption() {
return (
<RadioGroup
key="display-format"
label="Display format"
onChange={this.handleFormatChange}
selectedValue={this.state.targetDisplayFormat}
>
<Radio label="Abbreviation" value={TimezoneDisplayFormat.ABBREVIATION} />
<Radio label="Name" value={TimezoneDisplayFormat.NAME} />
<Radio label="Offset" value={TimezoneDisplayFormat.OFFSET} />
<Radio label="Composite" value={TimezoneDisplayFormat.COMPOSITE} />
</RadioGroup>
);
}

private handleTimezoneChange = (timezone: string) => {
this.setState({ timezone });
};
}
8 changes: 3 additions & 5 deletions packages/labs/examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
"target": "es5",
"paths": {
"@blueprintjs/labs": ["../dist/index"],
"@blueprintjs/docs": ["../../docs/dist/index"]
"@blueprintjs/docs": ["../../docs/dist/index"],
"@blueprintjs/datetime": ["../../datetime/dist/index"]
}
},
"include": [
"../types/*"
]
}
}
5 changes: 5 additions & 0 deletions packages/labs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
"dependencies": {
"@blueprintjs/core": "^1.26.0",
"classnames": "^2.2",
"fuzzaldrin-plus": "^0.5.0",
"moment": "^2.14.1",
"moment-timezone": "^0.5.13",
"popper.js": "1.11.0",
"pure-render-decorator": "^1.2.1",
"react-popper": "^0.7.2",
"tslib": "^1.5.0"
},
"devDependencies": {
"@types/fuzzaldrin-plus": "^0.0.1",
"@types/moment-timezone": "^0.2.35",
"bourbon": "^4.3.4",
"react": "^15.6.1",
"react-addons-css-transition-group": "^15.6.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/labs/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export const SELECT = "pt-select";
export const SELECT_POPOVER = `${SELECT}-popover`;
export const TAG_INPUT = "pt-tag-input";
export const TAG_INPUT_ICON = `${TAG_INPUT}-icon`;
export const TIMEZONE_PICKER = "pt-timezone-picker";
export const TIMEZONE_PICKER_POPOVER = `${TIMEZONE_PICKER}-popover`;
1 change: 1 addition & 0 deletions packages/labs/src/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
@import "select/multi-select";
@import "select/select";
@import "tag-input/tag-input";
@import "timezone-picker/timezone-picker";
1 change: 1 addition & 0 deletions packages/labs/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from "./select/multiSelect";
export * from "./select/select";
export * from "./select/suggest";
export * from "./tag-input/tagInput";
export * from "./timezone-picker/timezonePicker";
4 changes: 3 additions & 1 deletion packages/labs/src/components/omnibox/omnibox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
Overlay,
Utils,
} from "@blueprintjs/core";
import { IListItemsProps, IQueryListRendererProps, ISelectItemRendererProps, QueryList } from "../";

import * as Classes from "../../common/classes";
import { IListItemsProps, IQueryListRendererProps, QueryList } from "../query-list/queryList";
import { ISelectItemRendererProps } from "../select/select";

export interface IOmniboxProps<T> extends IListItemsProps<T> {
/**
Expand Down
11 changes: 3 additions & 8 deletions packages/labs/src/components/select/multiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@ import * as PureRender from "pure-render-decorator";
import * as React from "react";

import { HTMLInputProps, IPopoverProps, Keys, Menu, Popover, Position, Utils } from "@blueprintjs/core";
import {
IListItemsProps,
IQueryListRendererProps,
ISelectItemRendererProps,
ITagInputProps,
QueryList,
TagInput,
} from "../";
import * as Classes from "../../common/classes";
import { IListItemsProps, IQueryListRendererProps, QueryList } from "../query-list/queryList";
import { ISelectItemRendererProps } from "../select/select";
import { ITagInputProps, TagInput } from "../tag-input/tagInput";

export interface IMultiSelectProps<T> extends IListItemsProps<T> {
/** Controlled selected values. */
Expand Down
37 changes: 31 additions & 6 deletions packages/labs/src/components/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import {
Position,
Utils,
} from "@blueprintjs/core";
import { IListItemsProps, IQueryListRendererProps, QueryList } from "../";
import * as Classes from "../../common/classes";
import { IListItemsProps, IQueryListRendererProps, QueryList } from "../query-list/queryList";

export interface ISelectProps<T> extends IListItemsProps<T> {
/**
Expand Down Expand Up @@ -79,6 +79,12 @@ export interface ISelectProps<T> extends IListItemsProps<T> {
* @default false
*/
resetOnClose?: boolean;

/**
* Callback invoked when the query value changes,
* through user input or when the filter is reset.
*/
onQueryChange?: (query: string) => void;
}

export interface ISelectItemRendererProps<T> {
Expand Down Expand Up @@ -115,15 +121,20 @@ export class Select<T> extends React.Component<ISelectProps<T>, ISelectState<T>>
return Select as new () => Select<T>;
}

public state: ISelectState<T> = { isOpen: false, query: "" };

private TypedQueryList = QueryList.ofType<T>();
private list: QueryList<T>;
private refHandlers = {
queryList: (ref: QueryList<T>) => (this.list = ref),
};
private previousFocusedElement: HTMLElement;

constructor(props?: ISelectProps<T>, context?: any) {
super(props, context);

const query = props && props.inputProps && props.inputProps.value !== undefined ? props.inputProps.value : "";
this.state = { isOpen: false, query };
}

public render() {
// omit props specific to this component, spread the rest.
const {
Expand All @@ -149,6 +160,13 @@ export class Select<T> extends React.Component<ISelectProps<T>, ISelectState<T>>
);
}

public componentWillReceiveProps(nextProps: ISelectProps<T>) {
const { inputProps: nextInputProps = {} } = nextProps;
if (nextInputProps.value !== undefined && this.state.query !== nextInputProps.value) {
this.setState({ query: nextInputProps.value });
Copy link
Contributor

Choose a reason for hiding this comment

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

nice! this seems like a valuable fix and could use a unit test (since Select has actual coverage).

in fact you could go so far as to pull this change to a separate PR focused solely on supporting controlled input value.

though in truth we probably want an obvious query prop instead of inputProps.value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going to start on this in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}
}

public componentDidUpdate(_prevProps: ISelectProps<T>, prevState: ISelectState<T>) {
if (this.state.isOpen && !prevState.isOpen && this.list != null) {
this.list.scrollActiveItemIntoView();
Expand Down Expand Up @@ -291,10 +309,17 @@ export class Select<T> extends React.Component<ISelectProps<T>, ISelectState<T>>
};

private handleQueryChange = (event: React.FormEvent<HTMLInputElement>) => {
const { inputProps = {} } = this.props;
this.setState({ query: event.currentTarget.value });
const { inputProps = {}, onQueryChange } = this.props;
const query = event.currentTarget.value;
this.setState({ query });
Utils.safeInvoke(inputProps.onChange, event);
Utils.safeInvoke(onQueryChange, query);
};

private resetQuery = () => this.setState({ activeItem: this.props.items[0], query: "" });
private resetQuery = () => {
const { items, onQueryChange } = this.props;
const query = "";
this.setState({ activeItem: items[0], query });
Utils.safeInvoke(onQueryChange, query);
};
}
3 changes: 2 additions & 1 deletion packages/labs/src/components/select/suggest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import {
Position,
Utils,
} from "@blueprintjs/core";
import { IListItemsProps, IQueryListRendererProps, ISelectItemRendererProps, QueryList } from "../";
import * as Classes from "../../common/classes";
import { IListItemsProps, IQueryListRendererProps, QueryList } from "../query-list/queryList";
import { ISelectItemRendererProps } from "../select/select";

export interface ISuggestProps<T> extends IListItemsProps<T> {
/**
Expand Down
14 changes: 14 additions & 0 deletions packages/labs/src/components/timezone-picker/_timezone-picker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
* Licensed under the BSD-3 License as modified (the “License”); you may obtain a copy
* of the license at https://github.com/palantir/blueprint/blob/master/LICENSE
* and https://github.com/palantir/blueprint/blob/master/PATENTS
*/

@import "~@blueprintjs/core/src/common/variables";
@import "~@blueprintjs/core/src/components/forms/common";
@import "~@blueprintjs/core/src/components/tag/common";

.pt-timezone-picker-popover {
min-width: 370px;
}
Loading