Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6186549
chore: [data-view, data-table, filter-chip] wip move dates onto date-…
Shreyag02 Sep 28, 2026
5d59778
chore: [data-view, data-table, filter-chip] compare dates by day and …
Shreyag02 Sep 28, 2026
94b3815
Merge branch 'main' into chore/calendar-preview-remove-dayjs
Shreyag02 Sep 28, 2026
a05872c
fix: [data-view, data-table] keep filter values and reject impossible…
Shreyag02 Sep 28, 2026
da945ae
chore: [changelog] note the date filter behaviour changes
Shreyag02 Sep 28, 2026
c8dc2d9
fix: [calendar-preview] reject an impossible day in every date form
Shreyag02 Sep 28, 2026
d162f07
fix: [data-view, data-table] filter a restored date filter by its date
Shreyag02 Sep 28, 2026
5b6db2b
feat: [filter-chip] render CalendarPreview for date filters
Shreyag02 Oct 1, 2026
8b6f98d
chore: [filter-chip] target the calendar-preview input in chip styles
Shreyag02 Oct 1, 2026
48fa1ea
feat: [filter-chip] restore slotProps, showCalendarIcon and onErrorCh…
Shreyag02 Oct 1, 2026
b6b64bd
chore: [filter-chip] document the calendarProps formatValue and slot …
Shreyag02 Oct 1, 2026
5d27fa3
fix: [calendar-preview] keep the popup shut when a controlled open cl…
Shreyag02 Oct 1, 2026
cfcb1c2
chore: [changelog] update the date filter and calendarProps notes
Shreyag02 Oct 1, 2026
cb0ef10
chore: [filter-chip] split the calendarProps demo into max date and c…
Shreyag02 Oct 1, 2026
23e6a41
fix: [calendar-preview, data-view, data-table] load on safari 15
Shreyag02 Oct 1, 2026
9002ba9
fix: [filter-chip] keep the date value stable and pass disabled to th…
Shreyag02 Oct 1, 2026
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
103 changes: 103 additions & 0 deletions apps/www/src/content/docs/(overview)/upgrading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,109 @@ One section per release, newest first, with only the changes that need action
from you. The full record of every release, features and fixes included, is
on [GitHub releases](https://github.com/raystack/apsara/releases).

## Unreleased: date filters compare whole days

`DataView`, `DataTable` and `FilterChip` read and compare dates as calendar
days, without dayjs. A date filter's control is
[CalendarPreview](/docs/components/calendar-preview) instead of `DatePicker`.
`Calendar`, `DatePicker` and `RangePicker` are still exported and do not change.

### 1. Check how your backend reads `stringValue`

A date filter's `stringValue` is a day key, `'2026-08-15'`. It used to be local
midnight as a UTC instant, `'2026-08-14T18:30:00.000Z'` for a viewer in India.
`value` does not change.

```ts
// Before
onTableQueryChange={query => {
// '2026-08-14T18:30:00.000Z'
const day = query.filters?.[0]?.stringValue?.slice(0, 10); // '2026-08-14'
}}

// After
onTableQueryChange={query => {
const day = query.filters?.[0]?.stringValue; // '2026-08-15'
}}
```

If your backend took the date part of the old string, it read the day before
for every viewer east of UTC. The new value fixes that. A backend that expects a
full timestamp may read a date-only string differently, so test it.

Saved filters and URL parameters that hold an old timestamp still load, as the
day that instant falls on in the viewer's time zone.

DataView and DataTable compare and write the day in the viewer's time zone. If
you set `calendarProps.timeZone` or `filterProps.calendar.timeZone` to another
zone, a picked day can shift by one: 1 October picked in `Asia/Tokyo` is 30
September for a viewer in New York. Leave `timeZone` unset for filters, or set
it to the viewer's zone.

### 2. Move `calendarProps` to CalendarPreview props

`FilterChip`'s `calendarProps` and `DataTable`'s `filterProps.calendar` take
CalendarPreview props now. `slotProps.popover` and `showCalendarIcon` work as
before. `slotProps.input` takes `CalendarPreview.Input` props, which have no
`value` or `defaultValue`. `onErrorChange` still reports `'Invalid date'`, but
the error clears only when the typed text is valid or empty, or a date is
committed. Closing the popup does not clear it.

| Removed | Replacement |
|---|---|
| `dateFormat` | `formatValue(date, timeZone)`, which returns the label |
| `slotProps.calendar` / `calendarProps` | `minDate`, `maxDate`, `isDateUnavailable`, `defaultMonth`, `yearRange` |
| `inputProps` | `slotProps.input` |
| `popoverProps` | `slotProps.popover` |

```tsx
// Before
<FilterChip
columnType="date"
calendarProps={{
dateFormat: "YYYY-MM-DD",
slotProps: { calendar: { disabled: { after: new Date() } } }
}}
/>

// After
<FilterChip
columnType="date"
calendarProps={{
formatValue: (date, timeZone) =>
date.toLocaleDateString("en-CA", { timeZone }),
maxDate: new Date()
}}
/>
```

`formatValue` changes only the label. The input still reads typed text in the
default formats, such as `15 Aug 2026`.

### 3. Handle a cleared date

A date filter can be cleared: click the selected day, or empty the input. The
chip then calls `onValueChange` with `''`. In `DataView` and `DataTable` the
chip stays, and the filter stops matching until a date is picked again. If you
render `FilterChip` yourself, handle `''`.

### 4. Expect different rows from some filters

These filters used to match by mistake, and now match differently:

- A date filter with no value used to filter to today. It is now dropped.
- A date filter passed in `query` used to filter to today. It now filters by its
own date.
- A date filter on a day that does not exist, such as `2026-02-30`, used to roll
over to 2 March. It is now dropped.
- A row with no date used to be read as today, so it could match any operator,
depending on the filter day. It now matches only `neq`.
- A row holding a day that does not exist used to roll over to a real date. It
now matches only `neq`, and the timeline does not draw it.
- A row holding a numeric string or a boolean used to be read as a date, such
as the year 1792 for `'1786752000000'`. It now matches only `neq`, and the
timeline does not draw it.

## 2.0: `Theme` is rewritten

`Theme` used to put its tokens on `<html>` from an effect, so it could not
Expand Down
25 changes: 20 additions & 5 deletions apps/www/src/content/docs/components/filter-chip/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,33 @@ export const autocompleteDemo = {
};
export const calendarPropsDemo = {
type: 'code',
code: `
tabs: [
{
name: 'Max date',
code: `
<FilterChip
label="Created"
leadingIcon={<Info />}
columnType="date"
calendarProps={{
dateFormat: "YYYY-MM-DD",
slotProps: {
calendar: { captionLayout: "dropdown" }
}
maxDate: new Date()
}}
/>`
},
{
name: 'Custom format',
code: `
<FilterChip
label="Created"
leadingIcon={<Info />}
columnType="date"
calendarProps={{
formatValue: (date, timeZone) =>
date.toLocaleDateString('en-CA', { timeZone })
}}
/>`
}
]
};
export const iconDemo = {
type: 'code',
Expand Down
2 changes: 1 addition & 1 deletion apps/www/src/content/docs/components/filter-chip/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Use `selectProps` to enable autocomplete search on select and multiselect filter

### Date with calendarProps

Use `calendarProps` to forward DatePicker options such as `dateFormat`, `timeZone` and `slotProps.calendar` to the chip's date control. `value`, `onSelect`, and `defaultValue` are owned by `FilterChip`, and `children` is excluded so the chip's input trigger isn't replaced.
Use `calendarProps` to forward [CalendarPreview](/docs/components/calendar-preview) props such as `timeZone`, `minDate` and `maxDate` to the chip's date control. `formatValue(date, timeZone)` returns the label for the selected date. The chip is day-only, so the callback gets no scale. `slotProps.input` and `slotProps.popover` go to the date input and its popup. `FilterChip` owns the value and the calendar parts, and closes the popup when a day is picked.

<Demo data={calendarPropsDemo} />

Expand Down
29 changes: 23 additions & 6 deletions apps/www/src/content/docs/components/filter-chip/props.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { CalendarPreviewInputProps } from '../calendar-preview/props';
import { PopoverContentProps } from '../popover/props';

export interface FilterChipProps {
/** Text label for the filter (required) */
label: string;
Expand Down Expand Up @@ -46,16 +49,30 @@ export interface FilterChipProps {
defaultSearchValue?: string;
};

/** Props forwarded to the underlying DatePicker for `columnType="date"`. Refer to DatePicker for full props list. `dateFormat` defaults to `"DD MMM YYYY"`. */
/** Props for the date control at `columnType="date"`. `timeZone` through `today` are CalendarPreview props. */
calendarProps?: {
dateFormat?: string;
showCalendarIcon?: boolean;
/** Formats the selected date for the input. The chip calls it with a `Date` and the `timeZone`. */
formatValue?: (date: Date, timeZone?: string) => string;
/** The zone the calendar reads days in. DataView and DataTable filter in the viewer's zone, so a different zone can shift the filter day. */
timeZone?: string;
minDate?: Date;
maxDate?: Date;
isDateUnavailable?: (date: Date) => boolean;
yearRange?: { from: number; to: number };
defaultMonth?: Date;
today?: Date;
/** Props for the date input (`CalendarPreview.Input`) and its popup (`CalendarPreview.Content`). `input.disabled` and `input.readOnly` also apply to the calendar. */
slotProps?: {
input?: Record<string, unknown>;
calendar?: Record<string, unknown>;
popover?: Record<string, unknown>;
input?: Omit<CalendarPreviewInputProps, 'field'>;
popover?: Omit<PopoverContentProps, 'children'>;
};
/**
* Shows the calendar icon in the date input.
* @default false
*/
showCalendarIcon?: boolean;
/** Called with a message when the typed date is invalid, and with `undefined` when it is valid again. */
onErrorChange?: (error: string | undefined) => void;
};

/** Additional CSS class names */
Expand Down
67 changes: 67 additions & 0 deletions packages/raystack/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,73 @@ and [Icons](https://apsara.raystack.io/docs/theme/icons).
`IconComponent`, `IconProps`, `IconProviderProps`, and `IconProvider`
itself.

### Date filters: DataView and DataTable compare whole days (BREAKING)

DataView, DataTable and FilterChip read, compare and format dates
through CalendarPreview's date-fns adapter instead of dayjs. A date
filter compares whole days, and a date that is missing or does not exist
no longer stands in for another day. `dayjs` stays a dependency, because
`Calendar`, `DatePicker` and `RangePicker` still import it.

#### Breaking changes

- **A date filter's `stringValue` is a day key.** It is `'2026-08-15'`,
not `'2026-08-14T18:30:00.000Z'`. The old value was local midnight as
a UTC instant, so a backend that read the date part got the previous
day for any viewer east of UTC. `value` passes through unchanged. If
your backend parses `stringValue`, check it: a date-only string is
valid ISO 8601, but a parser that expects a timestamp may read it
differently.
- **A date filter with no value is dropped.** dayjs read an empty value
as today, so clearing a date filter filtered the rows to today.
- **A date filter restored from `query` filters by its date.** It lost
its type when the query was loaded, so the date comparison got no
date, and dayjs read that as today.
- **A date filter holding a day that does not exist is dropped.** dayjs
rolled `2026-02-30` over to 2 March and filtered on that.
- **A row whose date is missing matches only `neq`.** dayjs read it as
today, so the row matched `eq`, `lte` and `gte` on a filter day of
today, `lt` and `lte` on a later filter day, and `gt` and `gte` on an
earlier one.
- **A row holding a numeric string or a boolean matches only `neq`.**
dayjs read `'1786752000000'` as the year 1792 and `true` as 1970, and
compared those. A timeline row holding one is not drawn.
- **A row holding a day that does not exist matches only `neq`.** dayjs
rolled it to a real date and compared that, so `2026-02-30` matched
`lt` and `lte` against a filter day after 2 March, and `2026-13-01`
matched `gt` and `gte` against a filter day before 1 January 2027. A
timeline row holding one is not drawn.
- **`neq` still matches a row with a missing or unreadable date.**

Saved filters and URL parameters that hold an ISO timestamp are still
read, as the day the instant falls on in the viewer's zone. There is no
migration: the next query a stored filter produces carries a day key.

### FilterChip: the date control is CalendarPreview (BREAKING)

A date FilterChip renders `CalendarPreview` instead of `DatePicker`, so
the date filters in DataView and DataTable do too. No component inside
Apsara renders `Calendar`, `DatePicker` or `RangePicker` now.

#### Breaking changes

- **`calendarProps` takes CalendarPreview props.** `FilterChipCalendarProps`
is `formatValue`, `timeZone`, `minDate`, `maxDate`, `isDateUnavailable`,
`yearRange`, `defaultMonth` and `today`, plus `slotProps.input`,
`slotProps.popover`, `showCalendarIcon` and `onErrorChange`.
`formatValue(date, timeZone)` takes the date and returns its label.
`slotProps.input` takes `CalendarPreview.Input` props, which have no
`value` or `defaultValue`. `onErrorChange` still reports
`'Invalid date'`, and its error no longer clears when the popup closes,
only when the typed text is valid or empty, or a date is committed. `dateFormat`, `slotProps.calendar`, `inputProps`,
`calendarProps` and `popoverProps` are gone. Replace `dateFormat` with
`formatValue`, move calendar options such as `disabled` days to
`minDate`, `maxDate` or `isDateUnavailable`, and move `inputProps` and
`popoverProps` to `slotProps.input` and `slotProps.popover`.
DataTable's `filterProps.calendar` has the same type.
- **A date filter can be cleared.** Clicking the selected day or emptying
the input clears it, and `onValueChange` receives `''`.

## 0.49.0

### Calendar / DatePicker / RangePicker improvements (PR #819)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ describe('CalendarPreview root', () => {
error.mockRestore();
});

it('does not warn when a controlled value that started empty is set', () => {
const error = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined);
const { rerender } = render(
<CalendarPreview today={TODAY} value={null}>
<CalendarPreview.Days />
</CalendarPreview>
);
rerender(
<CalendarPreview today={TODAY} value={new Date(2026, 4, 27)}>
<CalendarPreview.Days />
</CalendarPreview>
);
expect(error).not.toHaveBeenCalled();
error.mockRestore();
});

it('commits a clicked day and reports the period and the day acted on', () => {
const onValueChange = vi.fn();
const { container } = renderCalendar(undefined, { onValueChange });
Expand Down
Loading
Loading