-
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] Timezone Picker #1568
[Labs] Timezone Picker #1568
Changes from 1 commit
356743c
66c6edc
4a4aef2
b6d8302
456303a
7de36a1
67ca676
dad05d1
75dd866
a1ee5cb
5739478
f61607c
185a671
4bb8e12
50f8e78
0b0237c
25176ab
c08169f
3e4a482
8823063
4c2def9
e5dc667
ba10a57
86ade6b
cbf7ee3
72c143a
cd0f2ad
8671cc0
d0f1e4f
f6f8b93
c7b76d2
1946977
b359c1a
020d02f
0cc560c
44e70d1
4d55db2
f237bdc
1054cea
f6858a7
9400904
d2f7f78
e10b3d3
2114c6a
d9d00bc
f652ffe
dc85ca6
b586639
add9e49
900c07e
bbf0ff1
7fc4720
8782648
15947e6
5afc0d7
ef932e9
cd20736
b23c37c
4c8cb85
4ca31e4
7b96c8a
bf15ba5
11631b8
76cad64
bd9ef19
b2b4788
ff49db9
d427502
ab8d5d5
b8e6c32
68ce37c
7f5df58
b0be6b6
ffbb9d3
956b612
76fccc2
c208968
56bf58e
d5ab205
3453ce0
b14a7c2
248f670
2b37613
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,8 @@ import * as React from "react"; | |
|
||
import { | ||
AbstractComponent, | ||
Button, | ||
Classes as CoreClasses, | ||
Icon, | ||
IconName, | ||
IPopoverProps, | ||
IProps, | ||
|
@@ -64,6 +64,11 @@ export interface ITimezoneSelectProps extends IProps { | |
*/ | ||
showLocalTimezone?: boolean; | ||
|
||
/** | ||
* Custom renderer for the target element. | ||
*/ | ||
targetRenderer?: ITimezoneSelectTargetRenderer; | ||
|
||
/** | ||
* Format to use when displaying the selected (or default) timezone within the target element. | ||
* @default TimezoneDisplayFormat.OFFSET | ||
|
@@ -72,6 +77,7 @@ export interface ITimezoneSelectProps extends IProps { | |
|
||
/** | ||
* A space-delimited list of class names to pass along to the target element. | ||
* This prop is ignored when a `targetRenderer` is provided. | ||
*/ | ||
targetClassName?: string; | ||
|
||
|
@@ -93,6 +99,31 @@ export interface ITimezoneSelectProps extends IProps { | |
popoverProps?: Partial<IPopoverProps> & object; | ||
} | ||
|
||
export type ITimezoneSelectTargetRenderer = (targetProps: ITimezoneSelectTargetProps) => JSX.Element | null; | ||
|
||
export interface ITimezoneSelectTargetProps { | ||
/** The currently selected timezone. */ | ||
value: string | undefined; | ||
|
||
/** Initial timezone, when none is selected. */ | ||
defaultValue: string | undefined; | ||
|
||
/** Display version of the currently selected timezone. */ | ||
displayValue: string | undefined; | ||
|
||
/** Display version of the default timezone. */ | ||
defaultDisplayValue: string | undefined; | ||
|
||
/** Placeholder for when no timezone has been selected and there is no default. */ | ||
placeholder: string; | ||
|
||
/** Whether the target is intended to be non-interactive. */ | ||
disabled: boolean; | ||
|
||
/** Date to use when determining timezone offsets. */ | ||
date: Date; | ||
} | ||
|
||
export type TimezoneDisplayFormat = "offset" | "abbreviation" | "name"; | ||
export const TimezoneDisplayFormat = { | ||
ABBREVIATION: "abbreviation" as "abbreviation", | ||
|
@@ -101,7 +132,7 @@ export const TimezoneDisplayFormat = { | |
}; | ||
|
||
export interface ITimezoneSelectState { | ||
selectedTimezone?: string; | ||
value?: string; | ||
query?: string; | ||
} | ||
|
||
|
@@ -137,24 +168,21 @@ export class TimezoneSelect extends AbstractComponent<ITimezoneSelectProps, ITim | |
super(props, context); | ||
|
||
this.state = { | ||
selectedTimezone: props.value, | ||
value: props.value, | ||
}; | ||
|
||
this.updateTimezones(props); | ||
this.updateInitialTimezones(props); | ||
} | ||
|
||
public render() { | ||
const { className, disabled, popoverProps, targetClassName } = this.props; | ||
const { selectedTimezone } = this.state; | ||
const { className, disabled, popoverProps } = this.props; | ||
const { query } = this.state; | ||
|
||
const finalPopoverProps: Partial<IPopoverProps> & object = { | ||
...popoverProps, | ||
popoverClassName: classNames(popoverProps.popoverClassName, Classes.TIMEZONE_SELECT_POPOVER), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: prefer putting className prop last in arguments to |
||
}; | ||
const isPlaceholder = !selectedTimezone; | ||
const targetTextClasses = classNames({ [CoreClasses.TEXT_MUTED]: isPlaceholder }); | ||
const targetIconClasses = classNames(CoreClasses.ALIGN_RIGHT, { [CoreClasses.TEXT_MUTED]: isPlaceholder }); | ||
|
||
return ( | ||
<TypedSelect | ||
|
@@ -170,13 +198,7 @@ export class TimezoneSelect extends AbstractComponent<ITimezoneSelectProps, ITim | |
disabled={disabled} | ||
onQueryChange={this.handleQueryChange} | ||
> | ||
<button | ||
className={classNames(Classes.TIMEZONE_SELECT_TARGET, CoreClasses.INPUT, targetClassName)} | ||
disabled={disabled} | ||
> | ||
<span className={targetTextClasses}>{this.getTargetText()}</span> | ||
<Icon iconName="caret-down" className={targetIconClasses} /> | ||
</button> | ||
{this.renderTarget()} | ||
</TypedSelect> | ||
); | ||
} | ||
|
@@ -189,11 +211,55 @@ export class TimezoneSelect extends AbstractComponent<ITimezoneSelectProps, ITim | |
this.props.showLocalTimezone !== nextProps.showLocalTimezone) { | ||
this.updateInitialTimezones(nextProps); | ||
} | ||
if (this.state.selectedTimezone !== nextProps.value) { | ||
this.setState({ selectedTimezone: nextProps.value }); | ||
if (this.state.value !== nextProps.value) { | ||
this.setState({ value: nextProps.value }); | ||
} | ||
} | ||
|
||
private renderTarget() { | ||
const { | ||
date, | ||
disabled, | ||
targetRenderer, | ||
targetDisplayFormat = TimezoneDisplayFormat.OFFSET, | ||
defaultValue, | ||
defaultToLocalTimezone, | ||
placeholder, | ||
} = this.props; | ||
const { value } = this.state; | ||
|
||
const finalDefaultValue = defaultValue || (defaultToLocalTimezone && getLocalTimezone()); | ||
|
||
const finalPlaceholder = placeholder !== undefined | ||
? placeholder | ||
: formatTimezone(PLACEHOLDER_TIMEZONE, date, targetDisplayFormat); | ||
|
||
const finalTargetRenderer = targetRenderer || this.defaultTargetRenderer; | ||
return finalTargetRenderer({ | ||
date, | ||
defaultDisplayValue: formatTimezone(finalDefaultValue, date, targetDisplayFormat), | ||
defaultValue: finalDefaultValue, | ||
disabled, | ||
displayValue: formatTimezone(value, date, targetDisplayFormat), | ||
placeholder: finalPlaceholder, | ||
value, | ||
}); | ||
} | ||
|
||
private defaultTargetRenderer: ITimezoneSelectTargetRenderer = (targetProps) => { | ||
const { targetClassName } = this.props; | ||
const { displayValue, defaultDisplayValue, placeholder, disabled } = targetProps; | ||
|
||
return ( | ||
<Button | ||
className={classNames(Classes.TIMEZONE_SELECT_TARGET, targetClassName)} | ||
text={displayValue || defaultDisplayValue || placeholder} | ||
rightIconName="caret-down" | ||
disabled={disabled} | ||
/> | ||
); | ||
} | ||
|
||
private updateTimezones(props: ITimezoneSelectProps): void { | ||
const timezones = getTimezoneItems(props.date); | ||
this.timezones = timezones; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
why two lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
otherwise, if we inline timezones and swap the two lines, its a bug... this.timezoneToQueryCandidates = getTimezoneQueryCandidates(this.timezones, date);
this.timezones = getTimezoneItems(date); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough |
||
|
@@ -204,27 +270,6 @@ export class TimezoneSelect extends AbstractComponent<ITimezoneSelectProps, ITim | |
this.initialTimezones = getInitialTimezoneItems(props.date, props.showLocalTimezone); | ||
} | ||
|
||
private getTargetText(): string { | ||
const { | ||
date, | ||
defaultValue, | ||
defaultToLocalTimezone, | ||
targetDisplayFormat = TimezoneDisplayFormat.OFFSET, | ||
placeholder, | ||
} = this.props; | ||
const { selectedTimezone } = this.state; | ||
const timezone = selectedTimezone || defaultValue || (defaultToLocalTimezone && getLocalTimezone()); | ||
const timezoneExists = timezone && moment.tz.zone(timezone) != null; | ||
|
||
if (timezoneExists) { | ||
return formatTimezone(timezone, date, targetDisplayFormat); | ||
} else if (placeholder !== undefined) { | ||
return placeholder; | ||
} else { | ||
return formatTimezone(PLACEHOLDER_TIMEZONE, date, targetDisplayFormat); | ||
} | ||
} | ||
|
||
private filterTimezones = (query: string, items: ITimezoneItem[]): ITimezoneItem[] => { | ||
const normalizedQuery = normalizeText(query); | ||
return items.filter((item) => { | ||
|
@@ -255,7 +300,7 @@ export class TimezoneSelect extends AbstractComponent<ITimezoneSelectProps, ITim | |
|
||
private handleItemSelect = (timezone: ITimezoneItem) => { | ||
if (this.props.value === undefined) { | ||
this.setState({ selectedTimezone: timezone.timezone }); | ||
this.setState({ value: timezone.timezone }); | ||
} | ||
Utils.safeInvoke(this.props.onChange, timezone.timezone); | ||
} | ||
|
@@ -423,7 +468,15 @@ function getAbbreviation(timezone: string, timestamp: number): string { | |
return ""; | ||
} | ||
|
||
function formatTimezone(timezone: string, date: Date, targetDisplayFormat: TimezoneDisplayFormat): string { | ||
function formatTimezone( | ||
timezone: string | undefined, | ||
date: Date, | ||
targetDisplayFormat: TimezoneDisplayFormat, | ||
): string | undefined { | ||
if (!timezone || moment.tz.zone(timezone) == null) { | ||
return undefined; | ||
} | ||
|
||
switch (targetDisplayFormat) { | ||
case TimezoneDisplayFormat.ABBREVIATION: | ||
return moment.tz(date.getTime(), timezone).format("z"); | ||
|
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.
FWIW, blueprint does not use
strictNullChecks
, so this has no effect in the compiler.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 add it now though, to ease the transition 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.
yeah i agree, just calling out that it doesn't mean anything inside blueprint
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's the status of #325? would it make sense to try again 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.
it won't be any easier now! we should look into it for 2.0, a few of us should be able to churn through the migration in a day or two.