@tcn/ui-time-selector 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/components/date_range_panel/date_range_panel.d.ts +13 -0
  2. package/dist/components/date_range_panel/date_range_panel.d.ts.map +1 -0
  3. package/dist/components/date_range_panel/date_range_panel.js +71 -0
  4. package/dist/components/date_range_panel/date_range_panel.js.map +1 -0
  5. package/dist/components/date_range_panel/date_range_panel_presenter.d.ts +14 -0
  6. package/dist/components/date_range_panel/date_range_panel_presenter.d.ts.map +1 -0
  7. package/dist/components/date_range_panel/date_range_panel_presenter.js +24 -0
  8. package/dist/components/date_range_panel/date_range_panel_presenter.js.map +1 -0
  9. package/dist/components/preset_panel/preset_panel.d.ts +11 -0
  10. package/dist/components/preset_panel/preset_panel.d.ts.map +1 -0
  11. package/dist/components/preset_panel/preset_panel.js +52 -0
  12. package/dist/components/preset_panel/preset_panel.js.map +1 -0
  13. package/dist/components/preset_panel/preset_panel_presenter.d.ts +24 -0
  14. package/dist/components/preset_panel/preset_panel_presenter.d.ts.map +1 -0
  15. package/dist/components/preset_panel/preset_panel_presenter.js +39 -0
  16. package/dist/components/preset_panel/preset_panel_presenter.js.map +1 -0
  17. package/dist/components/time_selector/time_selector.d.ts +12 -0
  18. package/dist/components/time_selector/time_selector.d.ts.map +1 -0
  19. package/dist/components/time_selector/time_selector.js +105 -0
  20. package/dist/components/time_selector/time_selector.js.map +1 -0
  21. package/dist/components/time_selector/time_selector_presenter.d.ts +28 -0
  22. package/dist/components/time_selector/time_selector_presenter.d.ts.map +1 -0
  23. package/dist/components/time_selector/time_selector_presenter.js +66 -0
  24. package/dist/components/time_selector/time_selector_presenter.js.map +1 -0
  25. package/dist/components/timezone_footer/timezone_footer.d.ts +9 -0
  26. package/dist/components/timezone_footer/timezone_footer.d.ts.map +1 -0
  27. package/dist/components/timezone_footer/timezone_footer.js +78 -0
  28. package/dist/components/timezone_footer/timezone_footer.js.map +1 -0
  29. package/dist/components/timezone_footer/timezone_footer_presenter.d.ts +23 -0
  30. package/dist/components/timezone_footer/timezone_footer_presenter.d.ts.map +1 -0
  31. package/dist/components/timezone_footer/timezone_footer_presenter.js +56 -0
  32. package/dist/components/timezone_footer/timezone_footer_presenter.js.map +1 -0
  33. package/dist/components/utils.d.ts +32 -0
  34. package/dist/components/utils.d.ts.map +1 -0
  35. package/dist/components/utils.js +62 -0
  36. package/dist/components/utils.js.map +1 -0
  37. package/dist/date_range_panel.css +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +5 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/preset_panel.css +1 -0
  43. package/dist/time_selector.css +1 -0
  44. package/dist/timezone_footer.css +1 -0
  45. package/package.json +72 -0
  46. package/src/__stories__/time_selector.stories.tsx +99 -0
  47. package/src/components/date_range_panel/date_range_panel.module.css +31 -0
  48. package/src/components/date_range_panel/date_range_panel.tsx +87 -0
  49. package/src/components/date_range_panel/date_range_panel_presenter.ts +35 -0
  50. package/src/components/preset_panel/preset_panel.module.css +35 -0
  51. package/src/components/preset_panel/preset_panel.tsx +74 -0
  52. package/src/components/preset_panel/preset_panel_presenter.ts +68 -0
  53. package/src/components/time_selector/time_selector.module.css +27 -0
  54. package/src/components/time_selector/time_selector.tsx +100 -0
  55. package/src/components/time_selector/time_selector_presenter.ts +116 -0
  56. package/src/components/timezone_footer/timezone_footer.module.css +28 -0
  57. package/src/components/timezone_footer/timezone_footer.tsx +109 -0
  58. package/src/components/timezone_footer/timezone_footer_presenter.ts +83 -0
  59. package/src/components/utils.ts +95 -0
  60. package/src/index.ts +5 -0
  61. package/tsconfig.json +7 -0
  62. package/types/file_types.d.ts +54 -0
  63. package/types/react_color.d.ts +61 -0
@@ -0,0 +1,78 @@
1
+ import { jsxs as l, jsx as t } from "react/jsx-runtime";
2
+ import { useSignalValue as i } from "@tcn/state";
3
+ import { VStack as _, HStack as u, Spacer as b } from "@tcn/ui/stacks";
4
+ import { BodyText as c } from "@tcn/ui/typography";
5
+ import { Select as v, Option as S } from "@tcn/ui/inputs";
6
+ import { Button as w } from "@tcn/ui/actions";
7
+ import { useMemo as y } from "react";
8
+ import { ChevronDownIcon as T } from "@tcn/icons/chevron_down_icon.js";
9
+ import { ChevronUpIcon as C } from "@tcn/icons/chevron_up_icon.js";
10
+ import { getTimezoneOffsetMinutes as N, formatUTCOffset as x, getTimezoneAbbreviation as M } from "../utils.js";
11
+ import '../../timezone_footer.css';const k = "_utc-footer_5c1fa0d", A = "_utc-label_0f42c44", B = "_utc-offset_8a037cb", I = "_timezone-settings-button_cd0743e", L = "_timezone-select_0f93d05", V = "_timezone-option-offset_89f4289", s = { "utc-footer": k, "utc-label": A, "utc-offset": B, "timezone-settings-button": I, "timezone-select": L, "timezone-option-offset": V };
12
+ function K({
13
+ presenter: o,
14
+ timezones: m,
15
+ onTimezoneChange: d
16
+ }) {
17
+ const h = i(o.broadcasts.timezone), f = i(o.broadcasts.isSelectOpen), z = i(o.broadcasts.timezoneAbbreviation), p = i(o.broadcasts.timezoneLabel), g = i(o.broadcasts.utcOffset), O = y(() => {
18
+ const e = m ?? Intl.supportedValuesOf("timeZone"), r = /* @__PURE__ */ new Date();
19
+ return e.map((n) => {
20
+ const a = N(n, r);
21
+ return {
22
+ iana: n,
23
+ abbreviation: M(n, r),
24
+ utcOffset: x(a),
25
+ offsetMinutes: a
26
+ };
27
+ }).sort((n, a) => a.offsetMinutes - n.offsetMinutes);
28
+ }, [m]);
29
+ return /* @__PURE__ */ l(_, { className: s["utc-footer"], height: "auto", width: "100%", children: [
30
+ /* @__PURE__ */ l(u, { height: "auto", width: "100%", children: [
31
+ /* @__PURE__ */ t(c, { className: s["utc-label"], selectable: !1, size: "md", children: p }),
32
+ /* @__PURE__ */ t(b, {}),
33
+ /* @__PURE__ */ t(c, { selectable: !1, size: "md", className: s["utc-offset"], children: g }),
34
+ /* @__PURE__ */ l(
35
+ w,
36
+ {
37
+ size: "sm",
38
+ hierarchy: "secondary",
39
+ className: s["timezone-settings-button"],
40
+ onClick: () => o.toggleSelect(),
41
+ children: [
42
+ "Change time settings",
43
+ f ? /* @__PURE__ */ t(C, { size: "sm" }) : /* @__PURE__ */ t(T, { size: "sm" })
44
+ ]
45
+ }
46
+ )
47
+ ] }),
48
+ f && /* @__PURE__ */ t(
49
+ v,
50
+ {
51
+ value: h,
52
+ onChange: (e) => o.setTimezone(e, d),
53
+ hierarchy: "secondary",
54
+ className: s["timezone-select"],
55
+ size: "sm",
56
+ children: O.map((e) => /* @__PURE__ */ t(
57
+ S,
58
+ {
59
+ value: e.iana,
60
+ label: z,
61
+ keywords: [e.iana, e.abbreviation],
62
+ children: /* @__PURE__ */ l(u, { height: "auto", width: "100%", gap: "8px", children: [
63
+ /* @__PURE__ */ t(c, { selectable: !1, children: e.iana }),
64
+ /* @__PURE__ */ t(b, {}),
65
+ /* @__PURE__ */ t(c, { selectable: !1, children: e.abbreviation }),
66
+ /* @__PURE__ */ t(c, { className: s["timezone-option-offset"], selectable: !1, children: e.utcOffset })
67
+ ] })
68
+ },
69
+ e.iana
70
+ ))
71
+ }
72
+ )
73
+ ] });
74
+ }
75
+ export {
76
+ K as TimezoneFooter
77
+ };
78
+ //# sourceMappingURL=timezone_footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timezone_footer.js","sources":["../../../src/components/timezone_footer/timezone_footer.tsx"],"sourcesContent":["import { useSignalValue } from '@tcn/state';\nimport { HStack, Spacer, VStack } from '@tcn/ui/stacks';\nimport { BodyText } from '@tcn/ui/typography';\nimport { Option, Select } from '@tcn/ui/inputs';\nimport { Button } from '@tcn/ui/actions';\nimport { useMemo } from 'react';\nimport { ChevronDownIcon } from '@tcn/icons/chevron_down_icon.js';\nimport { ChevronUpIcon } from '@tcn/icons/chevron_up_icon.js';\nimport {\n formatUTCOffset,\n getTimezoneAbbreviation,\n getTimezoneOffsetMinutes,\n} from '../utils.js';\nimport { TimezoneFooterPresenter } from './timezone_footer_presenter.js';\nimport styles from './timezone_footer.module.css';\n\ninterface TimezoneOption {\n iana: string;\n abbreviation: string;\n utcOffset: string;\n offsetMinutes: number;\n}\n\nexport interface TimezoneFooterOwnProps {\n presenter: TimezoneFooterPresenter;\n timezones?: string[];\n onTimezoneChange?: (tz: string) => void;\n}\n\nexport type TimezoneFooterProps = TimezoneFooterOwnProps;\n\nexport function TimezoneFooter({\n presenter,\n timezones,\n onTimezoneChange,\n}: TimezoneFooterProps) {\n const selectedTimezone = useSignalValue(presenter.broadcasts.timezone);\n const isSelectOpen = useSignalValue(presenter.broadcasts.isSelectOpen);\n const timezoneAbbreviation = useSignalValue(presenter.broadcasts.timezoneAbbreviation);\n const timezoneLabel = useSignalValue(presenter.broadcasts.timezoneLabel);\n const utcOffset = useSignalValue(presenter.broadcasts.utcOffset);\n\n const timezoneOptions = useMemo<TimezoneOption[]>(() => {\n const ianas: string[] =\n timezones ?? ((Intl as any).supportedValuesOf('timeZone') as string[]);\n const now = new Date();\n return ianas\n .map(iana => {\n const offsetMinutes = getTimezoneOffsetMinutes(iana, now);\n return {\n iana,\n abbreviation: getTimezoneAbbreviation(iana, now),\n utcOffset: formatUTCOffset(offsetMinutes),\n offsetMinutes,\n };\n })\n .sort((a, b) => b.offsetMinutes - a.offsetMinutes);\n }, [timezones]);\n\n return (\n <VStack className={styles['utc-footer']} height=\"auto\" width=\"100%\">\n <HStack height=\"auto\" width=\"100%\">\n <BodyText className={styles['utc-label']} selectable={false} size=\"md\">\n {timezoneLabel}\n </BodyText>\n <Spacer />\n <BodyText selectable={false} size=\"md\" className={styles['utc-offset']}>\n {utcOffset}\n </BodyText>\n <Button\n size=\"sm\"\n hierarchy=\"secondary\"\n className={styles['timezone-settings-button']}\n onClick={() => presenter.toggleSelect()}\n >\n Change time settings\n {isSelectOpen ? <ChevronUpIcon size=\"sm\" /> : <ChevronDownIcon size=\"sm\" />}\n </Button>\n </HStack>\n {isSelectOpen && (\n <Select\n value={selectedTimezone}\n onChange={tz => presenter.setTimezone(tz, onTimezoneChange)}\n hierarchy=\"secondary\"\n className={styles['timezone-select']}\n size=\"sm\"\n >\n {timezoneOptions.map(tz => (\n <Option\n key={tz.iana}\n value={tz.iana}\n label={timezoneAbbreviation}\n keywords={[tz.iana, tz.abbreviation]}\n >\n <HStack height=\"auto\" width=\"100%\" gap=\"8px\">\n <BodyText selectable={false}>{tz.iana}</BodyText>\n <Spacer />\n <BodyText selectable={false}>{tz.abbreviation}</BodyText>\n <BodyText className={styles['timezone-option-offset']} selectable={false}>\n {tz.utcOffset}\n </BodyText>\n </HStack>\n </Option>\n ))}\n </Select>\n )}\n </VStack>\n );\n}\n"],"names":["TimezoneFooter","presenter","timezones","onTimezoneChange","selectedTimezone","useSignalValue","isSelectOpen","timezoneAbbreviation","timezoneLabel","utcOffset","timezoneOptions","useMemo","ianas","now","iana","offsetMinutes","getTimezoneOffsetMinutes","getTimezoneAbbreviation","formatUTCOffset","a","b","jsxs","VStack","styles","HStack","jsx","BodyText","Spacer","Button","ChevronUpIcon","ChevronDownIcon","Select","tz","Option"],"mappings":";;;;;;;;;;;AA+BO,SAASA,EAAe;AAAA,EAC7B,WAAAC;AAAA,EACA,WAAAC;AAAA,EACA,kBAAAC;AACF,GAAwB;AACtB,QAAMC,IAAmBC,EAAeJ,EAAU,WAAW,QAAQ,GAC/DK,IAAeD,EAAeJ,EAAU,WAAW,YAAY,GAC/DM,IAAuBF,EAAeJ,EAAU,WAAW,oBAAoB,GAC/EO,IAAgBH,EAAeJ,EAAU,WAAW,aAAa,GACjEQ,IAAYJ,EAAeJ,EAAU,WAAW,SAAS,GAEzDS,IAAkBC,EAA0B,MAAM;AACtD,UAAMC,IACJV,KAAe,KAAa,kBAAkB,UAAU,GACpDW,wBAAU,KAAA;AAChB,WAAOD,EACJ,IAAI,CAAAE,MAAQ;AACX,YAAMC,IAAgBC,EAAyBF,GAAMD,CAAG;AACxD,aAAO;AAAA,QACL,MAAAC;AAAA,QACA,cAAcG,EAAwBH,GAAMD,CAAG;AAAA,QAC/C,WAAWK,EAAgBH,CAAa;AAAA,QACxC,eAAAA;AAAA,MAAA;AAAA,IAEJ,CAAC,EACA,KAAK,CAACI,GAAGC,MAAMA,EAAE,gBAAgBD,EAAE,aAAa;AAAA,EACrD,GAAG,CAACjB,CAAS,CAAC;AAEd,SACE,gBAAAmB,EAACC,KAAO,WAAWC,EAAO,YAAY,GAAG,QAAO,QAAO,OAAM,QAC3D,UAAA;AAAA,IAAA,gBAAAF,EAACG,GAAA,EAAO,QAAO,QAAO,OAAM,QAC1B,UAAA;AAAA,MAAA,gBAAAC,EAACC,GAAA,EAAS,WAAWH,EAAO,WAAW,GAAG,YAAY,IAAO,MAAK,MAC/D,UAAAf,EAAA,CACH;AAAA,wBACCmB,GAAA,EAAO;AAAA,MACR,gBAAAF,EAACC,GAAA,EAAS,YAAY,IAAO,MAAK,MAAK,WAAWH,EAAO,YAAY,GAClE,UAAAd,EAAA,CACH;AAAA,MACA,gBAAAY;AAAA,QAACO;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,WAAWL,EAAO,0BAA0B;AAAA,UAC5C,SAAS,MAAMtB,EAAU,aAAA;AAAA,UAC1B,UAAA;AAAA,YAAA;AAAA,YAEEK,sBAAgBuB,GAAA,EAAc,MAAK,MAAK,IAAK,gBAAAJ,EAACK,GAAA,EAAgB,MAAK,KAAA,CAAK;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAC3E,GACF;AAAA,IACCxB,KACC,gBAAAmB;AAAA,MAACM;AAAA,MAAA;AAAA,QACC,OAAO3B;AAAA,QACP,UAAU,CAAA4B,MAAM/B,EAAU,YAAY+B,GAAI7B,CAAgB;AAAA,QAC1D,WAAU;AAAA,QACV,WAAWoB,EAAO,iBAAiB;AAAA,QACnC,MAAK;AAAA,QAEJ,UAAAb,EAAgB,IAAI,CAAAsB,MACnB,gBAAAP;AAAA,UAACQ;AAAA,UAAA;AAAA,YAEC,OAAOD,EAAG;AAAA,YACV,OAAOzB;AAAA,YACP,UAAU,CAACyB,EAAG,MAAMA,EAAG,YAAY;AAAA,YAEnC,4BAACR,GAAA,EAAO,QAAO,QAAO,OAAM,QAAO,KAAI,OACrC,UAAA;AAAA,cAAA,gBAAAC,EAACC,GAAA,EAAS,YAAY,IAAQ,UAAAM,EAAG,MAAK;AAAA,gCACrCL,GAAA,EAAO;AAAA,cACR,gBAAAF,EAACC,GAAA,EAAS,YAAY,IAAQ,YAAG,cAAa;AAAA,cAC9C,gBAAAD,EAACC,KAAS,WAAWH,EAAO,wBAAwB,GAAG,YAAY,IAChE,UAAAS,EAAG,UAAA,CACN;AAAA,YAAA,EAAA,CACF;AAAA,UAAA;AAAA,UAZKA,EAAG;AAAA,QAAA,CAcX;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GAEJ;AAEJ;"}
@@ -0,0 +1,23 @@
1
+ import { Signal, derive } from '@tcn/state';
2
+ export declare class TimezoneFooterPresenter {
3
+ private readonly _startDate;
4
+ private readonly _endDate;
5
+ private _timezone;
6
+ private _isSelectOpen;
7
+ private _timezoneAbbreviation;
8
+ private _timezoneLabel;
9
+ private _utcOffset;
10
+ readonly broadcasts: {
11
+ timezone: Signal<string>['broadcast'];
12
+ isSelectOpen: Signal<boolean>['broadcast'];
13
+ timezoneAbbreviation: ReturnType<typeof derive<string, string>>['broadcast'];
14
+ timezoneLabel: ReturnType<typeof derive<string, string>>['broadcast'];
15
+ utcOffset: ReturnType<typeof derive<string, string>>['broadcast'];
16
+ };
17
+ constructor(_startDate: Signal<Date | null>, _endDate: Signal<Date | null>);
18
+ getTimezone(): string;
19
+ getAbbreviation(): string;
20
+ toggleSelect(): void;
21
+ setTimezone(iana: string, onTimezoneChange?: (tz: string) => void): void;
22
+ }
23
+ //# sourceMappingURL=timezone_footer_presenter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timezone_footer_presenter.d.ts","sourceRoot":"","sources":["../../../src/components/timezone_footer/timezone_footer_presenter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAS5C,qBAAa,uBAAuB;IAgBhC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAhB3B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,qBAAqB,CAA4C;IACzE,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,UAAU,CAA4C;IAE9D,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,CAAC;QACtC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC;QAC3C,oBAAoB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAC7E,aAAa,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACtE,SAAS,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;KACnE,CAAC;gBAGiB,UAAU,EAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,EAC/B,QAAQ,EAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IAyBhD,WAAW,IAAI,MAAM;IAIrB,eAAe,IAAI,MAAM;IAIzB,YAAY;IAIZ,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;CAmBlE"}
@@ -0,0 +1,56 @@
1
+ import { Signal as a, derive as o } from "@tcn/state";
2
+ import { getTimezoneAbbreviation as m, formatUTCOffset as h, getTimezoneOffsetMinutes as l, unshiftFromDisplay as _, shiftForDisplay as c } from "../utils.js";
3
+ class d {
4
+ constructor(t, s) {
5
+ this._startDate = t, this._endDate = s;
6
+ const i = Intl.DateTimeFormat().resolvedOptions().timeZone;
7
+ this._timezone = new a(i), this._timezoneAbbreviation = o(
8
+ this._timezone,
9
+ (e) => m(e, /* @__PURE__ */ new Date())
10
+ ), this._timezoneLabel = o(
11
+ this._timezone,
12
+ (e) => `${e}, ${m(e, /* @__PURE__ */ new Date())}`
13
+ ), this._utcOffset = o(
14
+ this._timezone,
15
+ (e) => h(l(e, /* @__PURE__ */ new Date()))
16
+ ), this.broadcasts = {
17
+ timezone: this._timezone.broadcast,
18
+ isSelectOpen: this._isSelectOpen.broadcast,
19
+ timezoneAbbreviation: this._timezoneAbbreviation.broadcast,
20
+ timezoneLabel: this._timezoneLabel.broadcast,
21
+ utcOffset: this._utcOffset.broadcast
22
+ };
23
+ }
24
+ _timezone;
25
+ _isSelectOpen = new a(!1);
26
+ _timezoneAbbreviation;
27
+ _timezoneLabel;
28
+ _utcOffset;
29
+ broadcasts;
30
+ getTimezone() {
31
+ return this._timezone.get();
32
+ }
33
+ getAbbreviation() {
34
+ return this._timezoneAbbreviation.get();
35
+ }
36
+ toggleSelect() {
37
+ this._isSelectOpen.set(!this._isSelectOpen.get());
38
+ }
39
+ setTimezone(t, s) {
40
+ const i = this._timezone.get(), e = this._startDate.get();
41
+ if (e != null) {
42
+ const n = _(e, i);
43
+ this._startDate.set(c(n, t));
44
+ }
45
+ const r = this._endDate.get();
46
+ if (r != null) {
47
+ const n = _(r, i);
48
+ this._endDate.set(c(n, t));
49
+ }
50
+ this._timezone.set(t), this._isSelectOpen.set(!1), s?.(t);
51
+ }
52
+ }
53
+ export {
54
+ d as TimezoneFooterPresenter
55
+ };
56
+ //# sourceMappingURL=timezone_footer_presenter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timezone_footer_presenter.js","sources":["../../../src/components/timezone_footer/timezone_footer_presenter.ts"],"sourcesContent":["import { Signal, derive } from '@tcn/state';\nimport {\n formatUTCOffset,\n getTimezoneAbbreviation,\n getTimezoneOffsetMinutes,\n shiftForDisplay,\n unshiftFromDisplay,\n} from '../utils.js';\n\nexport class TimezoneFooterPresenter {\n private _timezone: Signal<string>;\n private _isSelectOpen = new Signal<boolean>(false);\n private _timezoneAbbreviation: ReturnType<typeof derive<string, string>>;\n private _timezoneLabel: ReturnType<typeof derive<string, string>>;\n private _utcOffset: ReturnType<typeof derive<string, string>>;\n\n readonly broadcasts: {\n timezone: Signal<string>['broadcast'];\n isSelectOpen: Signal<boolean>['broadcast'];\n timezoneAbbreviation: ReturnType<typeof derive<string, string>>['broadcast'];\n timezoneLabel: ReturnType<typeof derive<string, string>>['broadcast'];\n utcOffset: ReturnType<typeof derive<string, string>>['broadcast'];\n };\n\n constructor(\n private readonly _startDate: Signal<Date | null>,\n private readonly _endDate: Signal<Date | null>\n ) {\n const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone;\n this._timezone = new Signal<string>(browserTz);\n\n this._timezoneAbbreviation = derive(this._timezone, tz =>\n getTimezoneAbbreviation(tz, new Date())\n );\n this._timezoneLabel = derive(\n this._timezone,\n tz => `${tz}, ${getTimezoneAbbreviation(tz, new Date())}`\n );\n this._utcOffset = derive(this._timezone, tz =>\n formatUTCOffset(getTimezoneOffsetMinutes(tz, new Date()))\n );\n\n this.broadcasts = {\n timezone: this._timezone.broadcast,\n isSelectOpen: this._isSelectOpen.broadcast,\n timezoneAbbreviation: this._timezoneAbbreviation.broadcast,\n timezoneLabel: this._timezoneLabel.broadcast,\n utcOffset: this._utcOffset.broadcast,\n };\n }\n\n getTimezone(): string {\n return this._timezone.get();\n }\n\n getAbbreviation(): string {\n return this._timezoneAbbreviation.get();\n }\n\n toggleSelect() {\n this._isSelectOpen.set(!this._isSelectOpen.get());\n }\n\n setTimezone(iana: string, onTimezoneChange?: (tz: string) => void) {\n const oldTz = this._timezone.get();\n\n const currentStart = this._startDate.get();\n if (currentStart != null) {\n const realStart = unshiftFromDisplay(currentStart, oldTz);\n this._startDate.set(shiftForDisplay(realStart, iana));\n }\n\n const currentEnd = this._endDate.get();\n if (currentEnd != null) {\n const realEnd = unshiftFromDisplay(currentEnd, oldTz);\n this._endDate.set(shiftForDisplay(realEnd, iana));\n }\n\n this._timezone.set(iana);\n this._isSelectOpen.set(false);\n onTimezoneChange?.(iana);\n }\n}\n"],"names":["TimezoneFooterPresenter","_startDate","_endDate","browserTz","Signal","derive","tz","getTimezoneAbbreviation","formatUTCOffset","getTimezoneOffsetMinutes","iana","onTimezoneChange","oldTz","currentStart","realStart","unshiftFromDisplay","shiftForDisplay","currentEnd","realEnd"],"mappings":";;AASO,MAAMA,EAAwB;AAAA,EAenC,YACmBC,GACAC,GACjB;AAFiB,SAAA,aAAAD,GACA,KAAA,WAAAC;AAEjB,UAAMC,IAAY,KAAK,eAAA,EAAiB,kBAAkB;AAC1D,SAAK,YAAY,IAAIC,EAAeD,CAAS,GAE7C,KAAK,wBAAwBE;AAAA,MAAO,KAAK;AAAA,MAAW,CAAAC,MAClDC,EAAwBD,GAAI,oBAAI,MAAM;AAAA,IAAA,GAExC,KAAK,iBAAiBD;AAAA,MACpB,KAAK;AAAA,MACL,CAAAC,MAAM,GAAGA,CAAE,KAAKC,EAAwBD,GAAI,oBAAI,KAAA,CAAM,CAAC;AAAA,IAAA,GAEzD,KAAK,aAAaD;AAAA,MAAO,KAAK;AAAA,MAAW,OACvCG,EAAgBC,EAAyBH,GAAI,oBAAI,KAAA,CAAM,CAAC;AAAA,IAAA,GAG1D,KAAK,aAAa;AAAA,MAChB,UAAU,KAAK,UAAU;AAAA,MACzB,cAAc,KAAK,cAAc;AAAA,MACjC,sBAAsB,KAAK,sBAAsB;AAAA,MACjD,eAAe,KAAK,eAAe;AAAA,MACnC,WAAW,KAAK,WAAW;AAAA,IAAA;AAAA,EAE/B;AAAA,EAvCQ;AAAA,EACA,gBAAgB,IAAIF,EAAgB,EAAK;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EAEC;AAAA,EAmCT,cAAsB;AACpB,WAAO,KAAK,UAAU,IAAA;AAAA,EACxB;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,sBAAsB,IAAA;AAAA,EACpC;AAAA,EAEA,eAAe;AACb,SAAK,cAAc,IAAI,CAAC,KAAK,cAAc,KAAK;AAAA,EAClD;AAAA,EAEA,YAAYM,GAAcC,GAAyC;AACjE,UAAMC,IAAQ,KAAK,UAAU,IAAA,GAEvBC,IAAe,KAAK,WAAW,IAAA;AACrC,QAAIA,KAAgB,MAAM;AACxB,YAAMC,IAAYC,EAAmBF,GAAcD,CAAK;AACxD,WAAK,WAAW,IAAII,EAAgBF,GAAWJ,CAAI,CAAC;AAAA,IACtD;AAEA,UAAMO,IAAa,KAAK,SAAS,IAAA;AACjC,QAAIA,KAAc,MAAM;AACtB,YAAMC,IAAUH,EAAmBE,GAAYL,CAAK;AACpD,WAAK,SAAS,IAAII,EAAgBE,GAASR,CAAI,CAAC;AAAA,IAClD;AAEA,SAAK,UAAU,IAAIA,CAAI,GACvB,KAAK,cAAc,IAAI,EAAK,GAC5BC,IAAmBD,CAAI;AAAA,EACzB;AACF;"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Returns the timezone offset in minutes using the same sign convention as
3
+ * Date.getTimezoneOffset(): positive = behind UTC (e.g. CDT/UTC-5 → +300),
4
+ * negative = ahead of UTC (e.g. CEST/UTC+2 → -120).
5
+ */
6
+ export declare function getTimezoneOffsetMinutes(iana: string, date: Date): number;
7
+ /** Returns the short timezone abbreviation, e.g. "CDT", "CEST", "UTC". */
8
+ export declare function getTimezoneAbbreviation(iana: string, date: Date): string;
9
+ /**
10
+ * Formats an offset (in getTimezoneOffset sign convention) as "UTC+HH:MM" or "UTC-HH:MM".
11
+ * Positive offsetMinutes (behind UTC) → negative sign in display (e.g. +300 → "UTC-05:00").
12
+ */
13
+ export declare function formatUTCOffset(offsetMinutes: number): string;
14
+ /**
15
+ * Shifts a real UTC moment so that DatePickerInput (which always renders in the
16
+ * browser's local timezone) visually displays the wall-clock time of `targetIana`.
17
+ */
18
+ export declare function shiftForDisplay(date: Date, targetIana: string): Date;
19
+ /**
20
+ * Reverses shiftForDisplay: given a display date (shifted), recovers the real UTC moment.
21
+ */
22
+ export declare function unshiftFromDisplay(displayDate: Date, targetIana: string): Date;
23
+ export declare function formatDate(date: Date): string;
24
+ export interface PresetItem {
25
+ label: string;
26
+ minutes: number;
27
+ }
28
+ export declare function datesFromPreset(item: PresetItem): {
29
+ start: Date;
30
+ end: Date;
31
+ };
32
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/components/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAuBzE;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAMxE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAK9E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAS7C;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,CAI5E"}
@@ -0,0 +1,62 @@
1
+ function i(t, n) {
2
+ const o = new Intl.DateTimeFormat("en-US", {
3
+ timeZone: t,
4
+ year: "numeric",
5
+ month: "2-digit",
6
+ day: "2-digit",
7
+ hour: "2-digit",
8
+ minute: "2-digit",
9
+ second: "2-digit",
10
+ hour12: !1
11
+ }).formatToParts(n), e = (a) => parseInt(o.find((m) => m.type === a)?.value ?? "0", 10), r = e("hour") % 24, s = Date.UTC(
12
+ e("year"),
13
+ e("month") - 1,
14
+ e("day"),
15
+ r,
16
+ e("minute"),
17
+ e("second")
18
+ );
19
+ return Math.round((n.getTime() - s) / 6e4);
20
+ }
21
+ function f(t, n) {
22
+ return new Intl.DateTimeFormat("en-US", {
23
+ timeZone: t,
24
+ timeZoneName: "short"
25
+ }).formatToParts(n).find((e) => e.type === "timeZoneName")?.value ?? t;
26
+ }
27
+ function u(t) {
28
+ const n = t <= 0 ? "+" : "-", o = Math.abs(t), e = Math.floor(o / 60).toString().padStart(2, "0"), r = (o % 60).toString().padStart(2, "0");
29
+ return `UTC${n}${e}:${r}`;
30
+ }
31
+ function c(t, n) {
32
+ const o = t.getTimezoneOffset(), e = i(n, t), r = (o - e) * 6e4;
33
+ return new Date(t.getTime() + r);
34
+ }
35
+ function g(t, n) {
36
+ const o = t.getTimezoneOffset(), e = i(n, t), r = (o - e) * 6e4;
37
+ return new Date(t.getTime() - r);
38
+ }
39
+ function h(t) {
40
+ return t.toLocaleString("en-US", {
41
+ month: "short",
42
+ day: "numeric",
43
+ year: "numeric",
44
+ hour: "2-digit",
45
+ minute: "2-digit",
46
+ hour12: !1
47
+ });
48
+ }
49
+ function T(t) {
50
+ const n = /* @__PURE__ */ new Date();
51
+ return { start: new Date(n.getTime() - t.minutes * 60 * 1e3), end: n };
52
+ }
53
+ export {
54
+ T as datesFromPreset,
55
+ h as formatDate,
56
+ u as formatUTCOffset,
57
+ f as getTimezoneAbbreviation,
58
+ i as getTimezoneOffsetMinutes,
59
+ c as shiftForDisplay,
60
+ g as unshiftFromDisplay
61
+ };
62
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../src/components/utils.ts"],"sourcesContent":["/**\n * Returns the timezone offset in minutes using the same sign convention as\n * Date.getTimezoneOffset(): positive = behind UTC (e.g. CDT/UTC-5 → +300),\n * negative = ahead of UTC (e.g. CEST/UTC+2 → -120).\n */\nexport function getTimezoneOffsetMinutes(iana: string, date: Date): number {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: iana,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n }).formatToParts(date);\n const get = (type: string) =>\n parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);\n const hour = get('hour') % 24; // handle 24 → 0\n const tzMs = Date.UTC(\n get('year'),\n get('month') - 1,\n get('day'),\n hour,\n get('minute'),\n get('second')\n );\n return Math.round((date.getTime() - tzMs) / 60000);\n}\n\n/** Returns the short timezone abbreviation, e.g. \"CDT\", \"CEST\", \"UTC\". */\nexport function getTimezoneAbbreviation(iana: string, date: Date): string {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: iana,\n timeZoneName: 'short',\n }).formatToParts(date);\n return parts.find(p => p.type === 'timeZoneName')?.value ?? iana;\n}\n\n/**\n * Formats an offset (in getTimezoneOffset sign convention) as \"UTC+HH:MM\" or \"UTC-HH:MM\".\n * Positive offsetMinutes (behind UTC) → negative sign in display (e.g. +300 → \"UTC-05:00\").\n */\nexport function formatUTCOffset(offsetMinutes: number): string {\n const sign = offsetMinutes <= 0 ? '+' : '-';\n const abs = Math.abs(offsetMinutes);\n const hours = Math.floor(abs / 60)\n .toString()\n .padStart(2, '0');\n const mins = (abs % 60).toString().padStart(2, '0');\n return `UTC${sign}${hours}:${mins}`;\n}\n\n/**\n * Shifts a real UTC moment so that DatePickerInput (which always renders in the\n * browser's local timezone) visually displays the wall-clock time of `targetIana`.\n */\nexport function shiftForDisplay(date: Date, targetIana: string): Date {\n const browserOffsetMin = date.getTimezoneOffset();\n const targetOffsetMin = getTimezoneOffsetMinutes(targetIana, date);\n const shiftMs = (browserOffsetMin - targetOffsetMin) * 60000;\n return new Date(date.getTime() + shiftMs);\n}\n\n/**\n * Reverses shiftForDisplay: given a display date (shifted), recovers the real UTC moment.\n */\nexport function unshiftFromDisplay(displayDate: Date, targetIana: string): Date {\n const browserOffsetMin = displayDate.getTimezoneOffset();\n const targetOffsetMin = getTimezoneOffsetMinutes(targetIana, displayDate);\n const shiftMs = (browserOffsetMin - targetOffsetMin) * 60000;\n return new Date(displayDate.getTime() - shiftMs);\n}\n\nexport function formatDate(date: Date): string {\n return date.toLocaleString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n });\n}\n\nexport interface PresetItem {\n label: string;\n minutes: number;\n}\n\nexport function datesFromPreset(item: PresetItem): { start: Date; end: Date } {\n const end = new Date();\n const start = new Date(end.getTime() - item.minutes * 60 * 1000);\n return { start, end };\n}\n"],"names":["getTimezoneOffsetMinutes","iana","date","parts","get","type","p","hour","tzMs","getTimezoneAbbreviation","formatUTCOffset","offsetMinutes","sign","abs","hours","mins","shiftForDisplay","targetIana","browserOffsetMin","targetOffsetMin","shiftMs","unshiftFromDisplay","displayDate","formatDate","datesFromPreset","item","end"],"mappings":"AAKO,SAASA,EAAyBC,GAAcC,GAAoB;AACzE,QAAMC,IAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,IAC7C,UAAUF;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EAAA,CACT,EAAE,cAAcC,CAAI,GACfE,IAAM,CAACC,MACX,SAASF,EAAM,KAAK,CAAAG,MAAKA,EAAE,SAASD,CAAI,GAAG,SAAS,KAAK,EAAE,GACvDE,IAAOH,EAAI,MAAM,IAAI,IACrBI,IAAO,KAAK;AAAA,IAChBJ,EAAI,MAAM;AAAA,IACVA,EAAI,OAAO,IAAI;AAAA,IACfA,EAAI,KAAK;AAAA,IACTG;AAAA,IACAH,EAAI,QAAQ;AAAA,IACZA,EAAI,QAAQ;AAAA,EAAA;AAEd,SAAO,KAAK,OAAOF,EAAK,QAAA,IAAYM,KAAQ,GAAK;AACnD;AAGO,SAASC,EAAwBR,GAAcC,GAAoB;AAKxE,SAJc,IAAI,KAAK,eAAe,SAAS;AAAA,IAC7C,UAAUD;AAAA,IACV,cAAc;AAAA,EAAA,CACf,EAAE,cAAcC,CAAI,EACR,KAAK,CAAAI,MAAKA,EAAE,SAAS,cAAc,GAAG,SAASL;AAC9D;AAMO,SAASS,EAAgBC,GAA+B;AAC7D,QAAMC,IAAOD,KAAiB,IAAI,MAAM,KAClCE,IAAM,KAAK,IAAIF,CAAa,GAC5BG,IAAQ,KAAK,MAAMD,IAAM,EAAE,EAC9B,WACA,SAAS,GAAG,GAAG,GACZE,KAAQF,IAAM,IAAI,WAAW,SAAS,GAAG,GAAG;AAClD,SAAO,MAAMD,CAAI,GAAGE,CAAK,IAAIC,CAAI;AACnC;AAMO,SAASC,EAAgBd,GAAYe,GAA0B;AACpE,QAAMC,IAAmBhB,EAAK,kBAAA,GACxBiB,IAAkBnB,EAAyBiB,GAAYf,CAAI,GAC3DkB,KAAWF,IAAmBC,KAAmB;AACvD,SAAO,IAAI,KAAKjB,EAAK,QAAA,IAAYkB,CAAO;AAC1C;AAKO,SAASC,EAAmBC,GAAmBL,GAA0B;AAC9E,QAAMC,IAAmBI,EAAY,kBAAA,GAC/BH,IAAkBnB,EAAyBiB,GAAYK,CAAW,GAClEF,KAAWF,IAAmBC,KAAmB;AACvD,SAAO,IAAI,KAAKG,EAAY,QAAA,IAAYF,CAAO;AACjD;AAEO,SAASG,EAAWrB,GAAoB;AAC7C,SAAOA,EAAK,eAAe,SAAS;AAAA,IAClC,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EAAA,CACT;AACH;AAOO,SAASsB,EAAgBC,GAA8C;AAC5E,QAAMC,wBAAU,KAAA;AAEhB,SAAO,EAAE,OADK,IAAI,KAAKA,EAAI,YAAYD,EAAK,UAAU,KAAK,GAAI,GAC/C,KAAAC,EAAA;AAClB;"}
@@ -0,0 +1 @@
1
+ ._date-picker-panel_1e6c441{border-right:1px solid var(--tcn-color-border, #d1d5db);padding:8px;gap:8px;flex:1;align-items:flex-start;justify-content:flex-start;align-self:stretch}._date-picker-panel-title_bc33101{font-weight:600;color:var(--tcn-color-text-primary, #111827);width:100%}._date-picker-section_90f5c3a{gap:4px;width:100%}._date-picker-label_113ebb5{font-weight:500;color:var(--tcn-color-text-secondary, #6b7280);display:flex;width:100%}._apply_05ed9af{margin-top:16px}
@@ -0,0 +1,3 @@
1
+ export { TimeSelector } from './components/time_selector/time_selector.js';
2
+ export type { TimeSelectorProps, TimeSelectorOwnProps, } from './components/time_selector/time_selector.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6CAA6C,CAAC;AAC3E,YAAY,EACV,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,6CAA6C,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { TimeSelector as r } from "./components/time_selector/time_selector.js";
2
+ export {
3
+ r as TimeSelector
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ ._presets-panel_7e3238f{flex:1;padding:8px;gap:0}._preset-search_113ae3a{margin-bottom:8px}._preset-list_191d980{overflow-x:hidden;overflow-y:auto;gap:2px}._preset-item_aa39cec{width:100%;padding:6px 8px;margin-left:6px;border-radius:4px;cursor:pointer}._preset-item_aa39cec:hover{background:var(--tcn-color-surface-hover, #f3f4f6)}._preset-item_aa39cec[data-selected=true]{background:var(--tcn-color-surface-selected, #eff6ff)}._preset-item_aa39cec[data-highlighted=true]{background:var(--tcn-color-surface-hover, #e5e7eb)}
@@ -0,0 +1 @@
1
+ ._trigger-abbreviation_8e22c20{color:var(--ergo-primary, #2563eb)}._trigger_1096f0b{display:inline-flex;align-items:center;border:1px solid var(--tcn-color-border, #d1d5db);border-radius:4px;padding:4px 8px;cursor:pointer;-webkit-user-select:none;user-select:none}._trigger_1096f0b:hover{border-color:var(--tcn-color-border-hover, #9ca3af)}._popper-content_9403264{background:var(--tcn-color-surface-primary, #ffffff);border:1px solid var(--tcn-color-border, #d1d5db);border-radius:6px;box-shadow:0 4px 16px #0000001f;overflow:hidden;align-items:flex-start;width:400px}
@@ -0,0 +1 @@
1
+ ._utc-footer_5c1fa0d{border-top:1px solid var(--tcn-color-border, #d1d5db);padding:8px 12px;gap:8px}._utc-label_0f42c44{color:var(--tcn-color-text-secondary, #6b7280)}._utc-offset_8a037cb{padding-right:4px}._timezone-settings-button_cd0743e{flex-shrink:0}._timezone-select_0f93d05{width:100%}._timezone-option-offset_89f4289{color:var(--tcn-color-text-secondary, #6b7280);min-width:80px;text-align:right;padding-right:4px}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@tcn/ui-time-selector",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "React time selector component",
6
+ "author": "TCN",
7
+ "license": "Apache-2.0",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "access": "public",
12
+ "blackcatConfig": {
13
+ "storybook": {
14
+ "publish": true,
15
+ "title": "UI Time Selector",
16
+ "port": 6301
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src",
22
+ "types",
23
+ "tsconfig.json"
24
+ ],
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "@bc-monorepo/source": "./src/index.ts",
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "sideEffects": [
37
+ "**/*.css"
38
+ ],
39
+ "dependencies": {
40
+ "@tcn/icons": "2.4.0",
41
+ "@tcn/state": "1.3.4"
42
+ },
43
+ "peerDependencies": {
44
+ "react": "^18.2.0",
45
+ "react-dom": "^18.2.0",
46
+ "@tcn/ui": "^0.18.1"
47
+ },
48
+ "devDependencies": {
49
+ "@types/react-color": "^3.0.13",
50
+ "@tcn/ui": "0.18.1"
51
+ },
52
+ "scripts": {
53
+ "start": "pnpm storybook",
54
+ "build": "vite build",
55
+ "clean": "rm -rf dist storybook-static",
56
+ "clean:all": "pnpm clean && rm -rf node_modules",
57
+ "check:all": "concurrently 'pnpm check:types' 'pnpm run biome check .'",
58
+ "check:types": "tsc --project tsconfig.typecheck.json --noEmit",
59
+ "check:format": "pnpm run biome format .",
60
+ "check:lint": "pnpm run biome lint .",
61
+ "check:imports": "pnpm run biome check --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
62
+ "fix:all": "pnpm run biome check --write",
63
+ "fix:format": "pnpm run biome format --write",
64
+ "fix:lint": "pnpm run biome lint --write",
65
+ "fix:imports": "pnpm run biome check --write --unsafe --formatter-enabled=false --linter-enabled=false --assist-enabled=true",
66
+ "publish:dry-run": "pnpm build && pnpm publish --dry-run --force --no-git-checks",
67
+ "biome": "pnpm exec biome",
68
+ "storybook": "bash ../../scripts/ensure-blackcat-addon-built.sh && storybook dev",
69
+ "storybook:silent": "storybook dev --no-open --disable-telemetry --quiet",
70
+ "storybook:build": "storybook build"
71
+ }
72
+ }
@@ -0,0 +1,99 @@
1
+ import type { Meta } from '@storybook/react-vite';
2
+ import { useState } from 'react';
3
+ import { TimeSelector } from '../components/time_selector/time_selector.js';
4
+
5
+ const meta: Meta<typeof TimeSelector> = {
6
+ title: 'TimeSelector',
7
+ component: TimeSelector,
8
+ };
9
+
10
+ export default meta;
11
+
12
+ function twoWeeksAgo(): Date {
13
+ const d = new Date();
14
+ d.setDate(d.getDate() - 14);
15
+ return d;
16
+ }
17
+
18
+ export function WithTwoWeekStartLimit() {
19
+ const [start, setStart] = useState<Date | null>(null);
20
+ const [end, setEnd] = useState<Date | null>(null);
21
+ const [timezone, setTimezone] = useState<string | null>(null);
22
+
23
+ return (
24
+ <div
25
+ style={{ position: 'relative', minHeight: '100vh', padding: '16px', width: '100%' }}
26
+ >
27
+ <div style={{ display: 'flex', justifyContent: 'center' }}>
28
+ <TimeSelector
29
+ startDateMin={twoWeeksAgo()}
30
+ onChange={(s, e) => {
31
+ setStart(s);
32
+ setEnd(e);
33
+ }}
34
+ onTimezoneChange={tz => setTimezone(tz)}
35
+ />
36
+ </div>
37
+ {start != null && end != null && (
38
+ <div
39
+ style={{
40
+ fontSize: '13px',
41
+ lineHeight: '1.6',
42
+ marginTop: '8px',
43
+ }}
44
+ >
45
+ <div>
46
+ <strong>Start:</strong> {start.toISOString()}
47
+ </div>
48
+ <div>
49
+ <strong>End:</strong> {end.toISOString()}
50
+ </div>
51
+ <div>
52
+ <strong>Timezone:</strong> {timezone ?? '—'}
53
+ </div>
54
+ </div>
55
+ )}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export function Basic() {
61
+ const [start, setStart] = useState<Date | null>(null);
62
+ const [end, setEnd] = useState<Date | null>(null);
63
+ const [timezone, setTimezone] = useState<string | null>(null);
64
+
65
+ return (
66
+ <div
67
+ style={{ position: 'relative', minHeight: '100vh', padding: '16px', width: '100%' }}
68
+ >
69
+ <div style={{ display: 'flex', justifyContent: 'center' }}>
70
+ <TimeSelector
71
+ onChange={(s, e) => {
72
+ setStart(s);
73
+ setEnd(e);
74
+ }}
75
+ onTimezoneChange={tz => setTimezone(tz)}
76
+ />
77
+ </div>
78
+ {start != null && end != null && (
79
+ <div
80
+ style={{
81
+ fontSize: '13px',
82
+ lineHeight: '1.6',
83
+ marginTop: '8px',
84
+ }}
85
+ >
86
+ <div>
87
+ <strong>Start:</strong> {start.toISOString()}
88
+ </div>
89
+ <div>
90
+ <strong>End:</strong> {end.toISOString()}
91
+ </div>
92
+ <div>
93
+ <strong>Timezone:</strong> {timezone ?? '—'}
94
+ </div>
95
+ </div>
96
+ )}
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,31 @@
1
+ .date-picker-panel {
2
+ border-right: 1px solid var(--tcn-color-border, #d1d5db);
3
+ padding: 8px;
4
+ gap: 8px;
5
+ flex: 1;
6
+ align-items: flex-start;
7
+ justify-content: flex-start;
8
+ align-self: stretch;
9
+ }
10
+
11
+ .date-picker-panel-title {
12
+ font-weight: 600;
13
+ color: var(--tcn-color-text-primary, #111827);
14
+ width: 100%;
15
+ }
16
+
17
+ .date-picker-section {
18
+ gap: 4px;
19
+ width: 100%;
20
+ }
21
+
22
+ .date-picker-label {
23
+ font-weight: 500;
24
+ color: var(--tcn-color-text-secondary, #6b7280);
25
+ display: flex;
26
+ width: 100%;
27
+ }
28
+
29
+ .apply {
30
+ margin-top: 16px;
31
+ }
@@ -0,0 +1,87 @@
1
+ import { useSignalValue } from '@tcn/state';
2
+ import { Spacer, VStack } from '@tcn/ui/stacks';
3
+ import { BodyText } from '@tcn/ui/typography';
4
+ import { DatePickerInput } from '@tcn/ui/inputs';
5
+ import { Button } from '@tcn/ui/actions';
6
+ import { useEffect, useRef } from 'react';
7
+ import { IteratorIcon } from '@tcn/icons/iterator_icon.js';
8
+ import { DateRangePanelPresenter } from './date_range_panel_presenter.js';
9
+ import styles from './date_range_panel.module.css';
10
+
11
+ export interface DateRangePanelOwnProps {
12
+ presenter: DateRangePanelPresenter;
13
+ focused: boolean;
14
+ startDateMin?: Date | null;
15
+ startDateMax?: Date | null;
16
+ endDateMin?: Date | null;
17
+ endDateMax?: Date | null;
18
+ onApply: () => void;
19
+ }
20
+
21
+ export type DateRangePanelProps = DateRangePanelOwnProps;
22
+
23
+ export function DateRangePanel({
24
+ presenter,
25
+ focused,
26
+ startDateMin,
27
+ startDateMax,
28
+ endDateMin,
29
+ endDateMax,
30
+ onApply,
31
+ }: DateRangePanelProps) {
32
+ const startDateRef = useRef<HTMLButtonElement>(null);
33
+
34
+ const startDate = useSignalValue(presenter.startDate.broadcast);
35
+ const endDate = useSignalValue(presenter.endDate.broadcast);
36
+ const isApplyDisabled = useSignalValue(presenter.broadcasts.isApplyDisabled);
37
+
38
+ useEffect(() => {
39
+ if (focused) {
40
+ startDateRef.current?.focus();
41
+ }
42
+ }, [focused]);
43
+
44
+ return (
45
+ <VStack className={styles['date-picker-panel']} height="auto">
46
+ <BodyText className={styles['date-picker-panel-title']} selectable={false}>
47
+ Absolute time range
48
+ </BodyText>
49
+ <VStack className={styles['date-picker-section']} height="auto" width="auto">
50
+ <BodyText className={styles['date-picker-label']} selectable={false}>
51
+ From
52
+ </BodyText>
53
+ <DatePickerInput
54
+ ref={startDateRef}
55
+ showTime
56
+ value={startDate}
57
+ onChange={(date: Date | null) => presenter.setStartDate(date)}
58
+ min={startDateMin ?? undefined}
59
+ max={startDateMax ?? undefined}
60
+ />
61
+ </VStack>
62
+ <VStack className={styles['date-picker-section']} height="auto" width="auto">
63
+ <BodyText className={styles['date-picker-label']} selectable={false}>
64
+ To
65
+ </BodyText>
66
+ <DatePickerInput
67
+ showTime
68
+ value={endDate}
69
+ onChange={(date: Date | null) => presenter.setEndDate(date)}
70
+ min={endDateMin ?? undefined}
71
+ max={endDateMax ?? undefined}
72
+ />
73
+ </VStack>
74
+ <Button
75
+ size="sm"
76
+ hierarchy="primary"
77
+ disabled={isApplyDisabled}
78
+ onClick={onApply}
79
+ className={styles['apply']}
80
+ >
81
+ Apply time range
82
+ <Spacer width="8px" />
83
+ <IteratorIcon size="sm" />
84
+ </Button>
85
+ </VStack>
86
+ );
87
+ }