add timezone selector component (#15880)
This commit is contained in:
parent
6d3e19d857
commit
b81f120916
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
const path = require('path');
|
||||
|
||||
// Suerset's webpack.config.js
|
||||
// Superset's webpack.config.js
|
||||
const customConfig = require('../webpack.config.js');
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ module.exports = {
|
|||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
['@babel/plugin-proposal-optional-chaining', { loose: true }],
|
||||
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose: true }],
|
||||
['@babel/plugin-transform-runtime', { corejs: 3 }],
|
||||
'react-hot-loader/babel',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -30044,6 +30044,14 @@
|
|||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.33",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz",
|
||||
"integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==",
|
||||
"requires": {
|
||||
"moment": ">= 2.9.0"
|
||||
}
|
||||
},
|
||||
"moo": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz",
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@
|
|||
"mathjs": "^8.0.1",
|
||||
"memoize-one": "^5.1.1",
|
||||
"moment": "^2.26.0",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"mousetrap": "^1.6.1",
|
||||
"mustache": "^2.2.1",
|
||||
"omnibar": "^2.1.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useArgs } from '@storybook/client-api';
|
||||
import TimezoneSelector, { TimezoneProps } from './index';
|
||||
|
||||
export default {
|
||||
title: 'TimezoneSelector',
|
||||
component: TimezoneSelector,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const InteractiveTimezoneSelector = (args: TimezoneProps) => {
|
||||
const [{ timezone }, updateArgs] = useArgs();
|
||||
const onTimezoneChange = (value: string) => {
|
||||
updateArgs({ timezone: value });
|
||||
};
|
||||
return (
|
||||
<TimezoneSelector timezone={timezone} onTimezoneChange={onTimezoneChange} />
|
||||
);
|
||||
};
|
||||
|
||||
InteractiveTimezoneSelector.args = {
|
||||
timezone: 'America/Los_Angeles',
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import { render } from 'spec/helpers/testing-library';
|
||||
import TimezoneSelector from './index';
|
||||
|
||||
describe('TimezoneSelector', () => {
|
||||
let timezone: string;
|
||||
const onTimezoneChange = jest.fn(zone => {
|
||||
timezone = zone;
|
||||
});
|
||||
it('renders a TimezoneSelector with a default if undefined', () => {
|
||||
jest.spyOn(moment.tz, 'guess').mockReturnValue('America/New_York');
|
||||
render(
|
||||
<TimezoneSelector
|
||||
onTimezoneChange={onTimezoneChange}
|
||||
timezone={timezone}
|
||||
/>,
|
||||
);
|
||||
expect(onTimezoneChange).toHaveBeenCalledWith('America/Nassau');
|
||||
});
|
||||
it('renders a TimezoneSelector with the closest value if passed in', async () => {
|
||||
render(
|
||||
<TimezoneSelector
|
||||
onTimezoneChange={onTimezoneChange}
|
||||
timezone="America/Los_Angeles"
|
||||
/>,
|
||||
);
|
||||
expect(onTimezoneChange).toHaveBeenLastCalledWith('America/Vancouver');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import { NativeGraySelect as Select } from 'src/components/Select';
|
||||
|
||||
const DEFAULT_TIMEZONE = 'GMT Standard Time';
|
||||
const MIN_SELECT_WIDTH = '375px';
|
||||
|
||||
const offsetsToName = {
|
||||
'-300-240': ['Eastern Standard Time', 'Eastern Daylight Time'],
|
||||
'-360-300': ['Central Standard Time', 'Central Daylight Time'],
|
||||
'-420-360': ['Mountain Standard Time', 'Mountain Daylight Time'],
|
||||
'-420-420': [
|
||||
'Mountain Standard Time - Phoenix',
|
||||
'Mountain Standard Time - Phoenix',
|
||||
],
|
||||
'-480-420': ['Pacific Standard Time', 'Pacific Daylight Time'],
|
||||
'-540-480': ['Alaska Standard Time', 'Alaska Daylight Time'],
|
||||
'-600-600': ['Hawaii Standard Time', 'Hawaii Daylight Time'],
|
||||
'60120': ['Central European Time', 'Central European Daylight Time'],
|
||||
'00': [DEFAULT_TIMEZONE, DEFAULT_TIMEZONE],
|
||||
'060': ['GMT Standard Time - London', 'British Summer Time'],
|
||||
};
|
||||
|
||||
const currentDate = moment();
|
||||
const JANUARY = moment([2021, 1]);
|
||||
const JULY = moment([2021, 7]);
|
||||
|
||||
const getOffsetKey = (name: string) =>
|
||||
JANUARY.tz(name).utcOffset().toString() +
|
||||
JULY.tz(name).utcOffset().toString();
|
||||
|
||||
const getTimezoneName = (name: string) => {
|
||||
const offsets = getOffsetKey(name);
|
||||
return (
|
||||
(currentDate.tz(name).isDST()
|
||||
? offsetsToName[offsets]?.[1]
|
||||
: offsetsToName[offsets]?.[0]) || name
|
||||
);
|
||||
};
|
||||
|
||||
export interface TimezoneProps {
|
||||
onTimezoneChange: (value: string) => void;
|
||||
timezone?: string | null;
|
||||
}
|
||||
|
||||
const ALL_ZONES = moment.tz
|
||||
.countries()
|
||||
.map(country => moment.tz.zonesForCountry(country, true))
|
||||
.flat();
|
||||
|
||||
const TIMEZONES: moment.MomentZoneOffset[] = [];
|
||||
ALL_ZONES.forEach(zone => {
|
||||
if (
|
||||
!TIMEZONES.find(
|
||||
option => getOffsetKey(option.name) === getOffsetKey(zone.name),
|
||||
)
|
||||
) {
|
||||
TIMEZONES.push(zone); // dedupe zones by offsets
|
||||
}
|
||||
});
|
||||
|
||||
const TIMEZONE_OPTIONS = TIMEZONES.sort(
|
||||
// sort by offset
|
||||
(a, b) =>
|
||||
moment.tz(currentDate, a.name).utcOffset() -
|
||||
moment.tz(currentDate, b.name).utcOffset(),
|
||||
).map(zone => ({
|
||||
label: `GMT ${moment
|
||||
.tz(currentDate, zone.name)
|
||||
.format('Z')} (${getTimezoneName(zone.name)})`,
|
||||
value: zone.name,
|
||||
offsets: getOffsetKey(zone.name),
|
||||
}));
|
||||
|
||||
const timezoneOptions = TIMEZONE_OPTIONS.map(option => (
|
||||
<Select.Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Select.Option>
|
||||
));
|
||||
|
||||
const TimezoneSelector = ({ onTimezoneChange, timezone }: TimezoneProps) => {
|
||||
const prevTimezone = useRef(timezone);
|
||||
const matchTimezoneToOptions = (timezone: string) =>
|
||||
TIMEZONE_OPTIONS.find(option => option.offsets === getOffsetKey(timezone))
|
||||
?.value || DEFAULT_TIMEZONE;
|
||||
|
||||
const updateTimezone = (tz: string) => {
|
||||
// update the ref to track changes
|
||||
prevTimezone.current = tz;
|
||||
// the parent component contains the state for the value
|
||||
onTimezoneChange(tz);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updatedTz = matchTimezoneToOptions(timezone || moment.tz.guess());
|
||||
if (prevTimezone.current !== updatedTz) {
|
||||
updateTimezone(updatedTz);
|
||||
}
|
||||
}, [timezone]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
css={{ minWidth: MIN_SELECT_WIDTH }} // smallest size for current values
|
||||
onChange={onTimezoneChange}
|
||||
value={timezone || DEFAULT_TIMEZONE}
|
||||
>
|
||||
{timezoneOptions}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimezoneSelector;
|
||||
Loading…
Reference in New Issue