@synerise/ds-date-range-picker 2.0.2 → 2.0.4

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.0.4](https://github.com/Synerise/synerise-design/compare/@synerise/ds-date-range-picker@2.0.3...@synerise/ds-date-range-picker@2.0.4) (2026-09-08)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **date-range-picker:** encode non-whole-hour timezone offsets correctly ([a51b393](https://github.com/Synerise/synerise-design/commit/a51b393cad84dcb2da67dcc3da2dabf7d22cf37a))
11
+
12
+ ## [2.0.3](https://github.com/Synerise/synerise-design/compare/@synerise/ds-date-range-picker@2.0.2...@synerise/ds-date-range-picker@2.0.3) (2026-09-07)
13
+
14
+ **Note:** Version bump only for package @synerise/ds-date-range-picker
15
+
6
16
  ## [2.0.2](https://github.com/Synerise/synerise-design/compare/@synerise/ds-date-range-picker@2.0.1...@synerise/ds-date-range-picker@2.0.2) (2026-09-02)
7
17
 
8
18
  **Note:** Version bump only for package @synerise/ds-date-range-picker
package/CLAUDE.md CHANGED
@@ -12,7 +12,7 @@ src/
12
12
  RawDateRangePicker.tsx — core picker logic without popover
13
13
  date.types.ts — DateRange, DateFilter, RelativeUnits, RangeKey
14
14
  constants.tsx — MODES, COLUMNS, presets, POPOVER_*_CONFIG
15
- utils.ts — normalizeRange, getDefaultTexts, toIsoString, DEFAULT_RANGE
15
+ utils.ts — normalizeRange, getDefaultTexts, DEFAULT_RANGE (toIsoString is re-exported from ds-core)
16
16
  fns.ts — re-exports / wrappers around date-fns
17
17
  RangePicker/ — absolute date picker (dual-month calendar)
18
18
  RelativeRangePicker/ — relative preset + custom range picker
@@ -114,7 +114,7 @@ Re-export of `format` from `date-fns` — use for consistent date formatting acr
114
114
 
115
115
  ### `utils`
116
116
 
117
- Namespace export: `normalizeRange`, `toIsoString`, `toIsoStringWithoutZone`, `getDefaultTexts`, `DEFAULT_RANGE`, `START_OF`, `END_OF`.
117
+ Namespace export: `normalizeRange`, `toIsoString` (re-exported from `@synerise/ds-core`), `toIsoStringWithoutZone`, `getDefaultTexts`, `DEFAULT_RANGE`, `START_OF`, `END_OF`.
118
118
 
119
119
  ### `CONST`
120
120
 
@@ -196,9 +196,9 @@ Addons (relative picker, filter) appear below the calendar and are toggled via `
196
196
 
197
197
  ## Key dependencies
198
198
 
199
- - `react-day-picker ^7` — calendar grid rendering inside `RangePicker`
199
+ - `react-day-picker ^10` — calendar grid rendering inside `RangePicker` (see the v10 notes in `ds-date-picker`'s CLAUDE.md; day cells now contain a `<button class="DayPicker-Day-Button">`)
200
200
  - `@synerise/ds-popover` — floating popover (floating-ui underneath)
201
- - `date-fns ^2` + `date-fns-tz 1.1.4` — date arithmetic and timezone support
201
+ - `date-fns ^2` — date arithmetic. Timezone handling is not this package's: `toIsoString` is re-exported from `@synerise/ds-core`, which owns the wall-clock convention and the `@date-fns/tz` dependency behind it
202
202
  - `dayjs ^1.8` — used in parts of `RangeFilter`
203
203
  - `ramda ^0.27` — functional utilities in `RangeFilter`
204
204
  - `react-intl` — i18n (peer dependency via host app)
@@ -210,6 +210,7 @@ Addons (relative picker, filter) appear below the calendar and are toggled via `
210
210
  - `valueTransformer` is applied inside `RawDateRangePicker` before calling `onApply` — default transformer strips `ALL_TIME`-specific props.
211
211
  - `forceAbsolute` converts relative `DateRange` values to absolute bounds before emitting — useful when downstream systems don't understand relative types.
212
212
  - `RangePicker` (absolute calendar) is a class-based `PureComponent` — avoid passing new function references on every render to prevent unnecessary re-renders.
213
+ - `getModifiers()` in `RangePicker/utils.ts` returns react-day-picker `Matcher`s keyed by modifier name; each key becomes a `DayPicker-Day--<key>` class. It normalises the `Date | string | null` range bounds to `Date`, which the v7 code did with casts.
213
214
  - `RelativeRangePicker` is also a class-based `PureComponent`.
214
215
  - The `SINCE` mode is a variant of relative dating anchored to a specific timestamp rather than "now".
215
216
  - `normalizeRange()` from `utils` converts relative ranges to absolute `Date` objects — used internally and safe to call from consuming code.
@@ -10,7 +10,7 @@ const Container = /* @__PURE__ */ styled.div.withConfig({
10
10
  const Row = /* @__PURE__ */ styled.div.withConfig({
11
11
  displayName: "RangeFormstyles__Row",
12
12
  componentId: "sc-4k3o5r-2"
13
- })(["width:100%;display:flex;justify-content:", ";min-height:32px;margin:8px 0;", " .ant-slider{width:100%;margin:24px 8px 8px;}.ant-tooltip-inner{white-space:nowrap;}&& .ant-slider-handle:focus,&& .ant-slider-handle:hover{.ant-tooltip-content{background-color:transparent;}.ant-tooltip-inner{color:", ";}}"], (props) => props.justifyContent, (props) => props.mode === "slider" && css(["&{column-gap:8px;margin-top:16px;", "{margin-top:14px;}}"], RemoveIconWrapper), (props) => props.theme.palette["grey-600"]);
13
+ })(["width:100%;display:flex;justify-content:", ";min-height:32px;margin:8px 0;", " .ant-slider{width:100%;margin:24px 8px 8px;}"], (props) => props.justifyContent, (props) => props.mode === "slider" && css(["&{column-gap:8px;margin-top:16px;", "{margin-top:14px;}}"], RemoveIconWrapper));
14
14
  const Separator = /* @__PURE__ */ styled.span.withConfig({
15
15
  displayName: "RangeFormstyles__Separator",
16
16
  componentId: "sc-4k3o5r-3"
@@ -2,7 +2,6 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import fnsIsSameDay from "date-fns/isSameDay";
3
3
  import fnsIsValid from "date-fns/isValid";
4
4
  import { PureComponent } from "react";
5
- import { DateUtils } from "react-day-picker";
6
5
  import { legacyParse } from "@date-fns/upgrade/v2";
7
6
  import { withDataFormat, getDefaultDataTimeOptions } from "@synerise/ds-core";
8
7
  import { YearPicker, MonthPicker, fnsDifferenceInYears, DayPicker, localeUtils, TimePicker, datePickerFormat } from "@synerise/ds-date-picker";
@@ -25,7 +24,7 @@ import "date-fns/endOfMonth";
25
24
  import "date-fns/format";
26
25
  import "date-fns/getYear";
27
26
  import fnsIsAfter from "date-fns/isAfter";
28
- import "date-fns/isBefore";
27
+ import fnsIsBefore from "date-fns/isBefore";
29
28
  import fnsIsSameMonth from "date-fns/isSameMonth";
30
29
  import "date-fns/isSameYear";
31
30
  import "date-fns/setDate";
@@ -35,6 +34,7 @@ import fnsStartOfDay from "date-fns/startOfDay";
35
34
  import "date-fns/startOfMonth";
36
35
  import { Sides, Side, PickerFooter, FooterSeparator, DateTimeModeSwitch } from "./RangePicker.styles.js";
37
36
  import { getModifiers, getDisabledTimeOptions, getSidesState } from "./utils.js";
37
+ const isDayBefore = (day, reference) => fnsIsBefore(fnsStartOfDay(day), fnsStartOfDay(reference));
38
38
  const NOOP = () => {
39
39
  };
40
40
  const TOOLTIP_FORMAT = "MMM d, yyyy, HH:mm";
@@ -51,10 +51,10 @@ function replaceRange(value, day) {
51
51
  }
52
52
  if (!from) {
53
53
  from = day;
54
- } else if (from && to && DateUtils.isSameDay(from, to) && DateUtils.isSameDay(day, from)) {
54
+ } else if (from && to && fnsIsSameDay(from, to) && fnsIsSameDay(day, from)) {
55
55
  from = null;
56
56
  to = null;
57
- } else if (to && DateUtils.isSameDay(day, to)) {
57
+ } else if (to && fnsIsSameDay(day, to)) {
58
58
  from = day;
59
59
  to = day;
60
60
  } else if (from && to) {
@@ -62,7 +62,7 @@ function replaceRange(value, day) {
62
62
  to = null;
63
63
  } else {
64
64
  to = day;
65
- if (DateUtils.isDayBefore(to, from)) {
65
+ if (isDayBefore(to, from)) {
66
66
  to = from;
67
67
  from = day;
68
68
  }
@@ -229,7 +229,7 @@ class RangePicker extends PureComponent {
229
229
  } = value;
230
230
  const rangeFrom = from ? legacyParse(from) : null;
231
231
  const rangeTo = to ? legacyParse(to) : null;
232
- const isSelectedRangeDay = !!rangeFrom && !!rangeTo && fnsIsValid(rangeFrom) && fnsIsValid(rangeTo) && !DateUtils.isDayBefore(day, rangeFrom) && !DateUtils.isDayBefore(rangeTo, day);
232
+ const isSelectedRangeDay = !!rangeFrom && !!rangeTo && fnsIsValid(rangeFrom) && fnsIsValid(rangeTo) && !isDayBefore(day, rangeFrom) && !isDayBefore(rangeTo, day);
233
233
  const dayForeground = /* @__PURE__ */ jsx(DayForeground, { className: "DayPicker-Day-FG" });
234
234
  return /* @__PURE__ */ jsxs(Fragment, { children: [
235
235
  /* @__PURE__ */ jsx(DayBackground, { className: "DayPicker-Day-BG" }),
@@ -273,44 +273,15 @@ class RangePicker extends PureComponent {
273
273
  type
274
274
  } = value;
275
275
  const modifiers = getModifiers(fnsIsValid(from) ? from : null, fnsIsValid(to) ? to : null, enteredTo);
276
- const selectedDays = fnsIsValid(from) && fnsIsValid(to) ? [from, {
277
- from,
278
- to
279
- }] : [void 0, {
280
- from: void 0,
281
- to: void 0
282
- }];
276
+ const selectedDays = fnsIsValid(from) && fnsIsValid(to) ? [legacyParse(from), {
277
+ from: legacyParse(from),
278
+ to: legacyParse(to)
279
+ }] : [];
283
280
  const parsedLeft = legacyParse(left.month);
284
281
  const parsedRight = legacyParse(right.month);
285
282
  const adjacentMonths = forceAdjacentMonths || fnsIsSameMonth(ADD.MONTHS(parsedLeft, 1), parsedRight);
286
283
  const adjacentYears = forceAdjacentMonths || fnsDifferenceInYears(ADD.MONTHS(parsedLeft, 1), parsedRight) === 0;
287
- return /* @__PURE__ */ jsx(
288
- DayPicker,
289
- {
290
- className: type.toLowerCase(),
291
- canChangeMonth: false,
292
- disabledDays: disabledDate,
293
- localeUtils,
294
- month: getDateFromString(sideState.month),
295
- title: sideState.monthTitle,
296
- hideLongNext: side === COLUMNS.LEFT && adjacentYears,
297
- hideShortNext: side === COLUMNS.LEFT && adjacentMonths,
298
- hideLongPrev: side === COLUMNS.RIGHT && adjacentYears,
299
- hideShortPrev: side === COLUMNS.RIGHT && adjacentMonths,
300
- renderDay: this.renderDay,
301
- onDayMouseEnter: this.handleDayMouseEnter,
302
- onDayMouseLeave: this.handleDayMouseLeave,
303
- onMonthNameClick: () => this.handleSideModeChange(side, "month"),
304
- onYearNameClick: () => this.handleSideModeChange(side, "year"),
305
- onMonthChange: (month) => this.handleSideMonthChange(side, month, "date"),
306
- fixedWeeks: true,
307
- showOutsideDay: true,
308
- modifiers,
309
- onDayClick: this.handleDayClick,
310
- selectedDays
311
- },
312
- `day_picker_${side}`
313
- );
284
+ return /* @__PURE__ */ jsx(DayPicker, { className: type.toLowerCase(), canChangeMonth: false, disabledDays: disabledDate, localeUtils, month: getDateFromString(sideState.month), title: sideState.monthTitle, hideLongNext: side === COLUMNS.LEFT && adjacentYears, hideShortNext: side === COLUMNS.LEFT && adjacentMonths, hideLongPrev: side === COLUMNS.RIGHT && adjacentYears, hideShortPrev: side === COLUMNS.RIGHT && adjacentMonths, renderDay: this.renderDay, onDayMouseEnter: this.handleDayMouseEnter, onDayMouseLeave: this.handleDayMouseLeave, onMonthNameClick: () => this.handleSideModeChange(side, "month"), onYearNameClick: () => this.handleSideModeChange(side, "year"), onMonthChange: (month) => this.handleSideMonthChange(side, month, "date"), fixedWeeks: true, showOutsideDays: true, modifiers, onDayClick: this.handleDayClick, selectedDays }, `day_picker_${side}`);
314
285
  };
315
286
  this.renderTimePicker = (side) => {
316
287
  const {
@@ -1,6 +1,6 @@
1
- import { Modifiers } from 'react-day-picker';
1
+ import { Matcher } from 'react-day-picker';
2
2
  import { DateRange, NullableDateLimit } from '../date.types';
3
3
  import { State } from './RangePicker.types';
4
4
  export declare const getDisabledTimeOptions: (initialDay: string | Date | undefined, granularity: string, initialLowerLimit?: NullableDateLimit, initialUpperLimit?: NullableDateLimit, is12HoursClock?: boolean) => number[];
5
5
  export declare const getSidesState: (value: DateRange, forceAdjacentMonths?: boolean) => State;
6
- export declare const getModifiers: (from: NullableDateLimit | undefined, to: NullableDateLimit | undefined, enteredTo: NullableDateLimit | undefined) => Modifiers;
6
+ export declare const getModifiers: (from: NullableDateLimit | undefined, to: NullableDateLimit | undefined, enteredTo: NullableDateLimit | undefined) => Record<string, Matcher | undefined>;
@@ -122,6 +122,12 @@ const getSidesState = (value, forceAdjacentMonths) => {
122
122
  }
123
123
  };
124
124
  };
125
+ const toDate = (value) => {
126
+ if (value === null || value === void 0) {
127
+ return void 0;
128
+ }
129
+ return value instanceof Date ? value : legacyParse(value);
130
+ };
125
131
  const getModifiers = (from, to, enteredTo) => {
126
132
  const isSelecting = from && !to && enteredTo;
127
133
  const enteredStart = isSelecting ? fnsMin([legacyParse(from), legacyParse(enteredTo)]) : enteredTo;
@@ -133,15 +139,14 @@ const getModifiers = (from, to, enteredTo) => {
133
139
  const startModifier = isSelecting && !!enteredTo && !!from && enteredTo < from ? void 0 : from;
134
140
  const endModifier = isSelecting && !!enteredTo && !!from && enteredTo < from ? from : to;
135
141
  return {
136
- start: startModifier,
137
- end: endModifier,
138
- entered,
139
- outside: void 0,
142
+ start: toDate(startModifier),
143
+ end: toDate(endModifier),
144
+ entered: typeof entered === "function" ? entered : toDate(entered),
140
145
  today: NOW,
141
- "entered-start": enteredStart,
142
- "entered-end": enteredEnd,
143
- "initial-entered": !endModifier ? startModifier : void 0,
144
- initial: !entered && !endModifier ? startModifier : void 0
146
+ "entered-start": toDate(enteredStart),
147
+ "entered-end": toDate(enteredEnd),
148
+ "initial-entered": !endModifier ? toDate(startModifier) : void 0,
149
+ initial: !entered && !endModifier ? toDate(startModifier) : void 0
145
150
  };
146
151
  };
147
152
  export {
@@ -16,7 +16,8 @@ import { isLifetime } from "./RelativeRangePicker/Elements/RangeDropdown/RangeDr
16
16
  import RelativeRangePicker from "./RelativeRangePicker/RelativeRangePicker.js";
17
17
  import { MODES, ALL_TIME, RELATIVE, ABSOLUTE, RELATIVE_PRESETS, ABSOLUTE_PRESETS, CUSTOM_RANGE_KEY } from "./constants.js";
18
18
  import relativeToAbsolute from "./dateUtils/relativeToAbsolute.js";
19
- import { normalizeRange, getDefaultTexts, toIsoString } from "./utils.js";
19
+ import { normalizeRange, getDefaultTexts } from "./utils.js";
20
+ import { toIsoString } from "@synerise/ds-core";
20
21
  const isRelative = (dateRange) => {
21
22
  const isLegacyCustom = Object.keys(dateRange).includes("key") && dateRange.key === void 0;
22
23
  return dateRange.key && (RELATIVE_PRESETS.map((preset) => preset.key).includes(dateRange.key) || dateRange.key === CUSTOM_RANGE_KEY) || isLegacyCustom;
package/dist/utils.d.ts CHANGED
@@ -4,7 +4,16 @@ import { DateRange } from './date.types';
4
4
  import { default as END_OF } from './dateUtils/endOf';
5
5
  import { default as START_OF } from './dateUtils/startOf';
6
6
  export { START_OF, END_OF };
7
- export declare function toIsoString(date: Date, timeZone?: string | undefined): string;
7
+ /**
8
+ * Re-exported rather than implemented here. This package used to carry its own copy, which took
9
+ * `Math.abs(offset) % 60` of an offset in *milliseconds* — so every zone that is not a whole number
10
+ * of hours ahead of UTC (India, Adelaide, Kathmandu, Chatham, Newfoundland) encoded as `:00`
11
+ * minutes. It also looked the offset up against the date's UTC fields rather than the wall clock it
12
+ * carries, which picked the wrong side of a DST transition.
13
+ *
14
+ * Kept exported because `utils` is part of this package's public surface (`export * as utils`).
15
+ */
16
+ export { toIsoString } from '@synerise/ds-core';
8
17
  export declare function toIsoStringWithoutZone(date: Date): string;
9
18
  export declare const normalizeRange: (range: DateRange) => DateRange;
10
19
  export declare const getDefaultTexts: (intl: IntlShape, disableDefault?: boolean, texts?: Partial<Texts>) => Texts;
package/dist/utils.js CHANGED
@@ -1,4 +1,3 @@
1
- import { getTimezoneOffset } from "date-fns-tz";
2
1
  import fnsMax from "date-fns/max";
3
2
  import fnsMin from "date-fns/min";
4
3
  import omit from "lodash.omit";
@@ -6,20 +5,13 @@ import { ABSOLUTE, RELATIVE, CUSTOM_RANGE_KEY } from "./constants.js";
6
5
  import ADD from "./dateUtils/add.js";
7
6
  import END_OF from "./dateUtils/endOf.js";
8
7
  import START_OF from "./dateUtils/startOf.js";
8
+ import { toIsoString } from "@synerise/ds-core";
9
9
  const rmvTZOffset = (dateString) => {
10
10
  const date = dateString.toString();
11
11
  const finalDate = date.replace(/[+-]\d\d:\d\d$/, "");
12
12
  return finalDate;
13
13
  };
14
14
  const pad = (num) => (num < 10 ? "0" : "") + num;
15
- function toIsoString(date, timeZone = "UTC") {
16
- if (!timeZone) {
17
- return new Date(date).toISOString();
18
- }
19
- const timeZoneOffset = getTimezoneOffset(timeZone, date);
20
- const dif = timeZoneOffset >= 0 ? "+" : "-";
21
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${dif}${pad(Math.floor(Math.abs(timeZoneOffset) / 60 / 60 / 1e3))}:${pad(Math.abs(timeZoneOffset) % 60)}`;
22
- }
23
15
  function toIsoStringWithoutZone(date) {
24
16
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
25
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-date-range-picker",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Date-Range-Picker UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -43,28 +43,27 @@
43
43
  "types": "dist/index.d.ts",
44
44
  "dependencies": {
45
45
  "@date-fns/upgrade": "^1.0.3",
46
- "@synerise/ds-badge": "^2.0.1",
47
- "@synerise/ds-button": "^2.0.1",
48
- "@synerise/ds-button-group": "^2.0.1",
49
- "@synerise/ds-date-picker": "^2.0.1",
50
- "@synerise/ds-dropdown": "^2.0.1",
51
- "@synerise/ds-form-field": "^2.0.1",
46
+ "@synerise/ds-badge": "^2.0.2",
47
+ "@synerise/ds-button": "^2.0.2",
48
+ "@synerise/ds-button-group": "^2.0.2",
49
+ "@synerise/ds-date-picker": "^2.0.3",
50
+ "@synerise/ds-dropdown": "^2.0.3",
51
+ "@synerise/ds-form-field": "^2.0.2",
52
52
  "@synerise/ds-icon": "^2.1.0",
53
- "@synerise/ds-inline-edit": "^2.0.1",
54
- "@synerise/ds-input": "^2.0.1",
55
- "@synerise/ds-input-number": "^2.0.1",
56
- "@synerise/ds-list-item": "^2.0.1",
57
- "@synerise/ds-manageable-list": "^2.0.1",
58
- "@synerise/ds-popover": "^2.0.0",
53
+ "@synerise/ds-inline-edit": "^2.0.3",
54
+ "@synerise/ds-input": "^2.0.3",
55
+ "@synerise/ds-input-number": "^2.0.3",
56
+ "@synerise/ds-list-item": "^2.0.2",
57
+ "@synerise/ds-manageable-list": "^2.1.0",
58
+ "@synerise/ds-popover": "^2.0.1",
59
59
  "@synerise/ds-scrollbar": "^2.0.1",
60
- "@synerise/ds-select": "^2.0.2",
61
- "@synerise/ds-slider": "^2.0.1",
62
- "@synerise/ds-tag": "^2.0.1",
63
- "@synerise/ds-time-picker": "^2.0.1",
64
- "@synerise/ds-tooltip": "^2.0.1",
60
+ "@synerise/ds-select": "^2.0.4",
61
+ "@synerise/ds-slider": "^2.0.2",
62
+ "@synerise/ds-tag": "^2.0.2",
63
+ "@synerise/ds-time-picker": "^2.0.3",
64
+ "@synerise/ds-tooltip": "^2.0.2",
65
65
  "@synerise/ds-utils": "^2.0.0",
66
66
  "date-fns": "^2.16.1",
67
- "date-fns-tz": "1.1.4",
68
67
  "dayjs": "^1.8.35",
69
68
  "lodash.clonedeep": "^4.5.0",
70
69
  "lodash.flatten": "^4.4.0",
@@ -78,7 +77,7 @@
78
77
  "lodash.reverse": "^4.0.1",
79
78
  "lodash.values": "^4.3.0",
80
79
  "ramda": "^0.27.0",
81
- "react-day-picker": "^7.4.8",
80
+ "react-day-picker": "^10.0.1",
82
81
  "uuid": "^8.3.2"
83
82
  },
84
83
  "devDependencies": {
@@ -102,5 +101,5 @@
102
101
  "react-intl": ">=3.12.0 <= 6.8",
103
102
  "styled-components": "^5.3.3"
104
103
  },
105
- "gitHead": "1dc97a2bae9f1d20c3f35e6672d059d502c90d1a"
104
+ "gitHead": "e0dced8fd3a1194eff2fafc1b7e385b29238af88"
106
105
  }