diff --git a/apps/posts/src/views/comments/comment-fields.ts b/apps/posts/src/views/comments/comment-fields.ts index e9966fa1598..a0e16704a71 100644 --- a/apps/posts/src/views/comments/comment-fields.ts +++ b/apps/posts/src/views/comments/comment-fields.ts @@ -69,8 +69,7 @@ export const commentFields = defineFields({ ui: { label: 'Date', defaultOperator: DEFAULT_DATE_OPERATOR, - type: 'date', - className: 'w-full max-w-32' + type: 'date' }, codec: dateCodec() }, diff --git a/apps/posts/src/views/members/member-fields.ts b/apps/posts/src/views/members/member-fields.ts index a1eef83a4ed..3a8562fae2a 100644 --- a/apps/posts/src/views/members/member-fields.ts +++ b/apps/posts/src/views/members/member-fields.ts @@ -150,8 +150,7 @@ export const memberFields = defineFields({ ui: { label: 'Last seen', type: 'date', - defaultOperator: DEFAULT_DATE_OPERATOR, - className: 'w-40' + defaultOperator: DEFAULT_DATE_OPERATOR }, codec: dateCodec() }, @@ -160,8 +159,7 @@ export const memberFields = defineFields({ ui: { label: 'Created', type: 'date', - defaultOperator: DEFAULT_DATE_OPERATOR, - className: 'w-40' + defaultOperator: DEFAULT_DATE_OPERATOR }, codec: dateCodec() }, @@ -264,8 +262,7 @@ export const memberFields = defineFields({ ui: { label: 'Paid start date', type: 'date', - defaultOperator: DEFAULT_DATE_OPERATOR, - className: 'w-40' + defaultOperator: DEFAULT_DATE_OPERATOR }, metadata: { activeColumn: { @@ -281,8 +278,7 @@ export const memberFields = defineFields({ ui: { label: 'Next billing date', type: 'date', - defaultOperator: DEFAULT_DATE_OPERATOR, - className: 'w-40' + defaultOperator: DEFAULT_DATE_OPERATOR }, metadata: { activeColumn: { diff --git a/apps/shade/package.json b/apps/shade/package.json index ffbc10d6f73..681bd1660dc 100644 --- a/apps/shade/package.json +++ b/apps/shade/package.json @@ -127,9 +127,11 @@ "clsx": "2.1.1", "cmdk": "1.1.1", "color": "^5.0.3", + "date-fns": "4.1.0", "lucide-react": "0.577.0", "moment-timezone": "^0.5.48", "react": "18.3.1", + "react-day-picker": "9.14.0", "react-dom": "18.3.1", "react-dropzone": "14.2.3", "react-hook-form": "7.72.1", diff --git a/apps/shade/src/components.ts b/apps/shade/src/components.ts index ab46c0000f4..d61635d907e 100644 --- a/apps/shade/src/components.ts +++ b/apps/shade/src/components.ts @@ -6,6 +6,7 @@ export * from './components/ui/badge'; export * from './components/ui/banner'; export * from './components/ui/breadcrumb'; export * from './components/ui/button'; +export * from './components/ui/calendar'; export * from './components/ui/card'; export * from './components/ui/chart'; export * from './components/ui/checkbox'; diff --git a/apps/shade/src/components/features/filters/filters.tsx b/apps/shade/src/components/features/filters/filters.tsx index 3c83d33620a..7f7e48c5949 100644 --- a/apps/shade/src/components/features/filters/filters.tsx +++ b/apps/shade/src/components/features/filters/filters.tsx @@ -1,7 +1,8 @@ 'use client'; import type React from 'react'; -import {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import {Calendar} from '@/components/ui/calendar'; import { Command, CommandEmpty, @@ -21,7 +22,7 @@ import {Popover, PopoverContent, PopoverTrigger} from '@/components/ui/popover'; import {Switch} from '@/components/ui/switch'; import {Tooltip, TooltipContent, TooltipTrigger} from '@/components/ui/tooltip'; import {cva, type VariantProps} from 'class-variance-authority'; -import {AlertCircle, Check, Loader2, Plus, X} from 'lucide-react'; +import {AlertCircle, Calendar as CalendarIcon, Check, Loader2, Plus, X} from 'lucide-react'; import {cn} from '@/lib/utils'; // i18n Configuration Interface @@ -642,6 +643,214 @@ function FilterInput({ ); } +// Parses an HTML-date-input value (YYYY-MM-DD) into a local-time Date so it +// matches what the native control would have produced. Returns undefined for +// empty / unparseable input rather than throwing. +const parseFilterDateValue = (value: string): Date | undefined => { + const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); + if (!match) { + return undefined; + } + const [, yearPart, monthPart, dayPart] = match; + const year = Number(yearPart); + const month = Number(monthPart); + const day = Number(dayPart); + const date = new Date(year, month - 1, day); + + if ( + date.getFullYear() !== year || + date.getMonth() !== month - 1 || + date.getDate() !== day + ) { + return undefined; + } + + return date; +}; + +const formatFilterDateValue = (date: Date | undefined): string => { + if (!date) { + return ''; + } + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +}; + +interface FilterDatePickerProps { + field?: FilterFieldConfig; + value: string; + onChange: (value: string) => void; + className?: string; +} + +// Composes a text input for YYYY-MM-DD values with a Shade Calendar popover. +// Avoid using here: Safari opens its native date picker +// from clicks inside the text area even when the calendar indicator is hidden. +function FilterDatePicker({ + field, + value, + onChange, + className +}: FilterDatePickerProps) { + const context = useFilterContext(); + const [open, setOpen] = useState(false); + const parsed = useMemo(() => parseFilterDateValue(value), [value]); + const [month, setMonth] = useState(parsed); + const inputRef = useRef(null); + const lastLocalCommitRef = useRef(value); + // Local buffer for the input's value so the controlled element follows the + // user's segment-edit state instead of the filter state. This insulates the + // input from upstream re-renders triggered by URL roundtrips on Comments — + // each keystroke updates `localValue` (which matches what the browser put in + // the DOM), so React never has to force the DOM back to the committed + // value mid-edit and the segment-edit cursor stays intact. + const [localValue, setLocalValue] = useState(value); + + useEffect(() => { + if (parsed) { + setMonth(parsed); + } + }, [parsed]); + + // Sync the buffer from the committed filter value only when the user + // isn't editing — calendar picks, "Clear filters", URL deep-links, etc. + useEffect(() => { + if (value === lastLocalCommitRef.current) { + return; + } + + if (document.activeElement !== inputRef.current) { + setLocalValue(value); + lastLocalCommitRef.current = value; + } + }, [value]); + + const notifyInputChange = (nextValue: string, input: HTMLInputElement | null = inputRef.current) => { + if (!field?.onInputChange || !input) { + return; + } + + field.onInputChange({ + target: {...input, value: nextValue}, + currentTarget: {...input, value: nextValue} + } as React.ChangeEvent); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setLocalValue(e.target.value); + }; + + const handleInputBlur = (e: React.FocusEvent) => { + const inputValue = e.target.value; + const parsedInputValue = parseFilterDateValue(inputValue); + const dateValue = inputValue && !parsedInputValue ? formatFilterDateValue(new Date()) : inputValue; + + if (parsedInputValue) { + setMonth(parsedInputValue); + } else if (dateValue) { + setMonth(parseFilterDateValue(dateValue)); + } + + if (dateValue !== inputValue) { + if (inputRef.current) { + inputRef.current.value = dateValue; + } + setLocalValue(dateValue); + } + + if (dateValue !== value) { + lastLocalCommitRef.current = dateValue; + onChange(dateValue); + } + notifyInputChange(dateValue, e.target); + }; + + const handleSelect = (date: Date | undefined) => { + if (!date) { + lastLocalCommitRef.current = ''; + if (inputRef.current) { + inputRef.current.value = ''; + } + setLocalValue(''); + onChange(''); + notifyInputChange(''); + return; + } + const formatted = formatFilterDateValue(date); + lastLocalCommitRef.current = formatted; + if (inputRef.current) { + inputRef.current.value = formatted; + } + setMonth(date); + setLocalValue(formatted); + onChange(formatted); + notifyInputChange(formatted); + setOpen(false); + }; + + return ( +
+ {field?.prefix && ( +
+ {field.prefix} +
+ )} +
+ +
+ + + + + + + + +
+ ); +} + interface FilterRemoveButtonProps extends React.ButtonHTMLAttributes, VariantProps { @@ -1733,13 +1942,11 @@ function FilterValueSelector({field, values, onChange, operator}: F cursorPointer: context.cursorPointer })} > - onChange([e.target.value, endDate] as T[])} - onInputChange={field.onInputChange} + onChange={v => onChange([v, endDate] as T[])} />
({field, values, onChange, operator}: F > {context.i18n.to}
- onChange([startDate, e.target.value] as T[])} - onInputChange={field.onInputChange} + onChange={v => onChange([startDate, v] as T[])} /> ); @@ -1824,13 +2029,11 @@ function FilterValueSelector({field, values, onChange, operator}: F if (field.type === 'date') { return ( - onChange([e.target.value] as T[])} - onInputChange={field.onInputChange} + onChange={v => onChange([v] as T[])} /> ); } diff --git a/apps/shade/src/components/ui/calendar.stories.tsx b/apps/shade/src/components/ui/calendar.stories.tsx new file mode 100644 index 00000000000..c2f55495d0b --- /dev/null +++ b/apps/shade/src/components/ui/calendar.stories.tsx @@ -0,0 +1,73 @@ +import type {Meta, StoryObj} from '@storybook/react-vite'; +import {useState} from 'react'; +import {Calendar} from './calendar'; +import {Popover, PopoverContent, PopoverTrigger} from './popover'; +import {Button} from './button'; + +const meta = { + title: 'Components / Calendar', + component: Calendar, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: + 'Date picker calendar built on react-day-picker. Pair with a Popover to compose a date input control.' + } + } + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const SingleCalendarExample = () => { + const [date, setDate] = useState(new Date()); + return ( + + ); +}; + +const DatePickerExample = () => { + const [date, setDate] = useState(undefined); + return ( +
+ + + + + + + + +
+ ); +}; + +export const Default: Story = { + parameters: { + docs: { + description: { + story: 'Inline single-date selection.' + } + } + }, + render: () => +}; + +export const DatePicker: Story = { + parameters: { + docs: { + description: { + story: 'Calendar inside a Popover, the typical date-picker composition.' + } + } + }, + render: () => +}; diff --git a/apps/shade/src/components/ui/calendar.tsx b/apps/shade/src/components/ui/calendar.tsx new file mode 100644 index 00000000000..a0f4e0a59a0 --- /dev/null +++ b/apps/shade/src/components/ui/calendar.tsx @@ -0,0 +1,193 @@ +import * as React from 'react'; +import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon} from 'lucide-react'; +import {DayButton, DayPicker, getDefaultClassNames} from 'react-day-picker'; + +import {cn} from '@/lib/utils'; +import {Button, buttonVariants} from '@/components/ui/button'; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = 'label', + buttonVariant = 'ghost', + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps['variant'] +}) { + const defaultClassNames = getDefaultClassNames(); + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + classNames={{ + root: cn('w-fit', defaultClassNames.root), + months: cn( + 'flex gap-4 flex-col md:flex-row relative', + defaultClassNames.months + ), + month: cn('flex flex-col w-full gap-4', defaultClassNames.month), + nav: cn( + 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between', + defaultClassNames.nav + ), + button_previous: cn( + buttonVariants({variant: buttonVariant}), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_previous + ), + button_next: cn( + buttonVariants({variant: buttonVariant}), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_next + ), + month_caption: cn( + 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)', + defaultClassNames.month_caption + ), + dropdowns: cn( + 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5', + defaultClassNames.dropdowns + ), + dropdown_root: cn( + 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md', + defaultClassNames.dropdown_root + ), + dropdown: cn('absolute bg-popover inset-0 opacity-0', defaultClassNames.dropdown), + caption_label: cn( + 'select-none font-medium', + captionLayout === 'label' + ? 'text-sm' + : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5', + defaultClassNames.caption_label + ), + table: 'w-full border-collapse', + weekdays: cn('flex', defaultClassNames.weekdays), + weekday: cn( + 'text-muted-foreground rounded-md w-(--cell-size) font-normal text-sm select-none', + defaultClassNames.weekday + ), + week: cn('flex w-full mt-2', defaultClassNames.week), + week_number_header: cn( + 'select-none w-(--cell-size)', + defaultClassNames.week_number_header + ), + week_number: cn( + 'text-[0.8rem] select-none text-muted-foreground', + defaultClassNames.week_number + ), + day: cn( + 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none', + defaultClassNames.day + ), + range_start: cn('rounded-l-md bg-accent', defaultClassNames.range_start), + range_middle: cn('rounded-none', defaultClassNames.range_middle), + range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end), + today: cn( + 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none', + defaultClassNames.today + ), + outside: cn( + 'text-muted-foreground aria-selected:text-muted-foreground', + defaultClassNames.outside + ), + disabled: cn( + 'text-muted-foreground opacity-50', + defaultClassNames.disabled + ), + hidden: cn('invisible', defaultClassNames.hidden), + ...classNames + }} + components={{ + Root: ({className: rootClassName, rootRef, ...rootProps}) => { + return ( +
+ ); + }, + Chevron: ({className: chevronClassName, orientation, ...chevronProps}) => { + if (orientation === 'left') { + return ; + } + if (orientation === 'right') { + return ; + } + return ; + }, + DayButton: CalendarDayButton, + WeekNumber: ({children, ...weekProps}) => { + return ( + +
+ {children} +
+ + ); + }, + ...components + }} + formatters={{ + formatMonthDropdown: date => date.toLocaleString('default', {month: 'short'}), + ...formatters + }} + showOutsideDays={showOutsideDays} + {...props} + /> + ); +} +Calendar.displayName = 'Calendar'; + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames(); + + const ref = React.useRef(null); + React.useEffect(() => { + if (modifiers.focused) { + ref.current?.focus(); + } + }, [modifiers.focused]); + + return ( + +
+ ); + } +})); + type TestOption = { value: string; label: string; @@ -13,6 +33,12 @@ const ALL_OPTIONS: TestOption[] = [ {value: 'draft', label: 'Draft'} ]; +interface DateFiltersProps { + initialValue?: string; + onFiltersChange: ReturnType; + onInputChange: ReturnType; +} + function TestFilters({valueSource}: Readonly<{valueSource: ValueSource}>) { const [filters, setFilters] = useState([createFilter('status', 'is', ['published'])]); const fields = useMemo(() => ([ @@ -46,6 +72,35 @@ function StaticLoadingFilters({isLoading, options}: Readonly<{isLoading: boolean return fields={fields} filters={filters} showSearchInput={false} onChange={setFilters} />; } +function DateFilters({ + initialValue = '2026-05-07', + onFiltersChange, + onInputChange +}: Readonly) { + const [filters, setFilters] = useState([createFilter('created_at', 'is', [initialValue])]); + const fields = useMemo[]>(() => ([ + { + key: 'created_at', + label: 'Date', + type: 'date' as const, + operators: [{value: 'is', label: 'is'}], + onInputChange: event => onInputChange(event.target.value) + } + ]), [onInputChange]); + + return ( + + fields={fields} + filters={filters} + showSearchInput={false} + onChange={(nextFilters) => { + setFilters(nextFilters); + onFiltersChange(String(nextFilters[0]?.values[0] || '')); + }} + /> + ); +} + function getSelectedValueTrigger() { return screen.getByRole('button', {name: 'Published'}); } @@ -70,6 +125,10 @@ function createMatchingValueSource() { return {id: 'status', useOptions}; } +function openCalendar() { + fireEvent.click(screen.getByRole('button', {name: 'Open calendar'})); +} + describe('Filters ValueSource', () => { beforeAll(() => { global.ResizeObserver = class { @@ -213,4 +272,82 @@ describe('Filters ValueSource', () => { expect(await screen.findByPlaceholderText('Search status...')).toBeDefined(); expect(document.querySelector('.animate-spin')).toBeTruthy(); }); + + it('calls date field onInputChange when a typed date is committed', () => { + const handleFiltersChange = vi.fn(); + const handleInputChange = vi.fn(); + + render(); + + const input = screen.getByDisplayValue('2026-05-07'); + fireEvent.change(input, {target: {value: '2026-05-08'}}); + fireEvent.blur(input); + + expect(handleFiltersChange).toHaveBeenCalledWith('2026-05-08'); + expect(handleInputChange).toHaveBeenCalledWith('2026-05-08'); + }); + + it('resets manually entered invalid date values to today', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 4, 9)); + const handleFiltersChange = vi.fn(); + const handleInputChange = vi.fn(); + + render(); + + const input = screen.getByDisplayValue('2026-05-07'); + fireEvent.change(input, {target: {value: '2026-02-30'}}); + fireEvent.blur(input); + + expect(screen.getByDisplayValue('2026-05-09')).toBeDefined(); + expect(handleFiltersChange).toHaveBeenCalledWith('2026-05-09'); + expect(handleInputChange).toHaveBeenCalledWith('2026-05-09'); + }); + + it('passes valid date values to the calendar selection', async () => { + render(); + + openCalendar(); + + expect((await screen.findByTestId('calendar-selected')).getAttribute('data-selected')).toBe('2026-05-07'); + }); + + it('updates the date input when a calendar date is selected', async () => { + const handleFiltersChange = vi.fn(); + const handleInputChange = vi.fn(); + + render(); + + openCalendar(); + fireEvent.click(await screen.findByRole('button', {name: 'Select May 8'})); + + expect(screen.getByDisplayValue('2026-05-08')).toBeDefined(); + expect(handleFiltersChange).toHaveBeenCalledWith('2026-05-08'); + expect(handleInputChange).toHaveBeenCalledWith('2026-05-08'); + }); + + it('uses an editable text input for date values', () => { + render(); + + const input = screen.getByDisplayValue('2026-05-07') as HTMLInputElement; + + expect(input.type).toBe('text'); + expect(input.pattern).toBe('\\d{4}-\\d{2}-\\d{2}'); + }); + + it('does not normalize overflow date values for the calendar selection', async () => { + render(); + + openCalendar(); + + expect((await screen.findByTestId('calendar-selected')).getAttribute('data-selected')).toBe(''); + }); + + it('requires date values to use the HTML date input format', async () => { + render(); + + openCalendar(); + + expect((await screen.findByTestId('calendar-selected')).getAttribute('data-selected')).toBe(''); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55cfed64032..baeca84ab14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1119,6 +1119,9 @@ importers: color: specifier: ^5.0.3 version: 5.0.3 + date-fns: + specifier: 4.1.0 + version: 4.1.0 lucide-react: specifier: 0.577.0 version: 0.577.0(react@18.3.1) @@ -1128,6 +1131,9 @@ importers: react: specifier: 18.3.1 version: 18.3.1 + react-day-picker: + specifier: 9.14.0 + version: 9.14.0(react@18.3.1) react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) @@ -3798,6 +3804,9 @@ packages: peerDependencies: postcss-selector-parser: ^6.0.10 + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + '@distributed-systems/callsite@1.1.1': resolution: {integrity: sha512-YSA3kWjClnLmFKNpdQCZlMQoWI4N6KpR/T4MaREEQczaehcagsVorT3YDV17KR6zuJXDs7f+kkSt1o/D6SufAQ==} @@ -8296,6 +8305,10 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@tabby_ai/hijri-converter@1.0.5': + resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==} + engines: {node: '>=16.0.0'} + '@tailwindcss/line-clamp@0.4.4': resolution: {integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==} peerDependencies: @@ -12420,10 +12433,16 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + date-format@4.0.14: resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} engines: {node: '>=4.0'} @@ -19041,7 +19060,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.14.2: @@ -19143,6 +19161,12 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + react-day-picker@9.14.0: + resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-docgen-typescript@2.4.0: resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} peerDependencies: @@ -24020,6 +24044,8 @@ snapshots: dependencies: postcss-selector-parser: 6.1.2 + '@date-fns/tz@1.4.1': {} + '@distributed-systems/callsite@1.1.1': dependencies: ee-log: 3.0.9 @@ -29234,6 +29260,8 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tabby_ai/hijri-converter@1.0.5': {} + '@tailwindcss/line-clamp@0.4.4(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3))': dependencies: tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.3) @@ -34899,10 +34927,14 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-jalali@4.1.0-0: {} + date-fns@2.30.0: dependencies: '@babel/runtime': 7.29.2 + date-fns@4.1.0: {} + date-format@4.0.14: {} date-time@2.1.0: @@ -44223,6 +44255,14 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-day-picker@9.14.0(react@18.3.1): + dependencies: + '@date-fns/tz': 1.4.1 + '@tabby_ai/hijri-converter': 1.0.5 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 18.3.1 + react-docgen-typescript@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3