gpbl / react-day-picker

DayPicker is a customizable date picker component for React. Add date pickers, calendars, and date inputs to your web applications.
https://daypicker.dev
MIT License
5.88k stars 702 forks source link

RootContext is missing pass-through types for each context it provides #2072

Closed union-zakbutcher closed 2 months ago

union-zakbutcher commented 3 months ago

Description

I'm getting the following type error which makes sense b/c of type defs included in this project:

Type '{ children: Element; selected: Date; }' is not assignable to type 'IntrinsicAttributes & RootContext'.
  Property 'selected' does not exist on type 'IntrinsicAttributes & RootContext'.

The provided type for RootContext is:

export interface RootContext {
  children?: ReactNode;
}

Reproduced in this fork

If the above link doesn't work, it should be reproducible with this code:

export default function App() {
  const [selectedDay, setSelectedDay] = React.useState<Date | undefined>();
  return (
    <RootProvider selected={new Date()}>
      <DayPicker
        mode="single"
        onSelect={setSelectedDay}
        selected={selectedDay}
      />
    </RootProvider>
  );
}

Expected Behavior

I expect the props (or RootContext) to accept all possible props, flagged as optional, for all included providers because all additional props are automatically spread into each provider via the following where initialProps is passed to the initialProps prop of each provider

const { children, ...initialProps } = props;

Providers:

Actual Behavior

I get the following TS error:

Type '{ children: Element; selected: Date; }' is not assignable to type 'IntrinsicAttributes & RootContext'.
  Property 'selected' does not exist on type 'IntrinsicAttributes & RootContext'.

Steps to Reproduce

See above setup (copied below) to use RootProvider as such:

export default function App() {
  const [selectedDay, setSelectedDay] = React.useState<Date | undefined>();
  return (
    <RootProvider selected={new Date()}>
      <DayPicker
        mode="single"
        onSelect={setSelectedDay}
        selected={selectedDay}
      />
    </RootProvider>
  );
}

Possible Solution

Extend each interface accordingly, e.g.:

type RootContextProps = Partial<DayPickerDefaultProps> | Partial<DayPickerSingleProps> | Partial<DayPickerMultipleProps> | Partial<DayPickerRangeProps>
/** The props of {@link RootProvider}. */
export type RootContext = RootContextProps & {
  children?: ReactNode;
}

Screenshots

From Fork image

From VS Code

image
union-zakbutcher commented 3 months ago

I've poked around a bit an know this isn't exactly an easy thing to fix due to TS, but if there is a path forward, it would be VERY good to fix because ultimately all of the props flow down to the correct components / contexts, it's just a matter of playing nice with TS

union-zakbutcher commented 3 months ago

If need-be, I can do a sym-link locally to test, but it's nearly 1am my time and I'm not feeling it right now. Lemme know if I need to do some more due diligence in my project before this is approved / merged

gpbl commented 3 months ago

Hey @union-zakbutcher thanks for the detailed report! From a first look, your PR looks good to go. Give me some time to review it more carefully.

Not sure what you are trying to do there with the context providers. I am working on v9 which would actually simplify these parts (and the typings): if you are relying a lot to the DayPicker v8 internals, you may have hard time to refactor to v9.

Would you like to help us get v9 ready / or do you have time to help us testing it?

union-zakbutcher commented 3 months ago

@gpbl I'm certainly open to the idea of helping develop / test v9 - I just can't make any firm commitments involving timeframes. If you have some general onboarding docs or something I can look at to get an idea of what help you need, I can take a look and get back to you on what I can actually help with!

As far as what I'm doing / needing, I was trying to resolve a type error in a storybook file where I'm documenting my custom implementation of react-day-picker to match the design system I'm building out for my company and I needed to use the RootProvider in those docs. The implementation is fairly large and private so sharing it here would be difficult, however I can try and summarize what I'm doing and we can go from there.

I've built two main components: Calendar and DatePicker. Calendar is my main wrapper around react-day-picker and handles low-level customizations like the theme color, some date formatting, our own custom context, and some other behavioral changes we need based on the UX we're targeting. Ultimately it boils down to a date-range picker and a single-date picker which use DayPicker with the necessary props to make it behave as a date range picker or single date picker, respectively. This component is JUST the UI / UX of the actual calendar

The DatePicker component is my higher-level, business-logic-included wrapper around Calendar that includes UX smoothing and enhances the calendar with other components from our DS like a TextInput for manually typing in a date and then updating Calendar accordingly or making sure our onChange callback is only called when a valid date has been entered so we don't erroneously call onChange with a partial or invalid date.

I'm not actually using RootProvider in my app, I'm just using it in my documentation in Storybook to force certain behaviors so I can "lock in" various permutations while using my components that depend on a context existing. I know all of the above is super vague so I've got a screenshot and the code from my documentation here to help out (note each const is associated with a heading in the image, e.g. the Caption element is the CalendarMonthNavigator heading):

screencapture-localhost-6006-2024-03-14-12_27_46

export const Caption = () => (
  <StoryWrapper col>
    <RootProvider>
      <CalendarMonthNavigator displayMonth={DATE} />
    </RootProvider>
  </StoryWrapper>
);
export const Day = () => (
  <StoryWrapper>
    <Col className="w-fit items-stretch">
      <Calendar>
        <Row className="justify-between">
          <Text.Body>Unselected Day</Text.Body>
          <RootProvider>
            <DayPickerProvider initialProps={{ mode: "single" }}>
              <div className="w-fit">
                <CalendarDay
                  date={subDays(DATE, 1)}
                  displayMonth={subDays(DATE, 1)}
                />
              </div>
            </DayPickerProvider>
          </RootProvider>
        </Row>
        <Row className="justify-between">
          <Text.Body>Selected Day</Text.Body>
          <RootProvider selected={DATE}>
            <DayPickerProvider initialProps={{ mode: "single" }}>
              <div className="w-fit">
                <CalendarDay date={DATE} displayMonth={DATE} />
              </div>
            </DayPickerProvider>
          </RootProvider>
        </Row>
        <Row className="justify-between">
          <Text.Body>Today (Unselected)</Text.Body>
          <RootProvider>
            <DayPickerProvider initialProps={{ mode: "single" }}>
              <div className="w-fit">
                <CalendarDay date={DATE} displayMonth={DATE} />
              </div>
            </DayPickerProvider>
          </RootProvider>
        </Row>
        <Row className="justify-between">
          <Text.Body>Date Range Start</Text.Body>
          <RootRangeProvider mode="range" selected={DATE_RANGE}>
            <div className="w-fit">
              <CalendarDay
                date={subDays(DATE, 2)}
                displayMonth={subDays(DATE, 2)}
              />
            </div>
          </RootRangeProvider>
        </Row>
        <Row className="justify-between">
          <Text.Body>Date Range Middle</Text.Body>
          <RootRangeProvider mode="range" selected={DATE_RANGE}>
            <div className="w-fit">
              <CalendarDay
                date={subDays(DATE, 1)}
                displayMonth={subDays(DATE, 1)}
              />
            </div>
          </RootRangeProvider>
        </Row>
        <Row className="justify-between">
          <Text.Body>Date Range End</Text.Body>
          <RootRangeProvider mode="range" selected={DATE_RANGE}>
            <div className="w-fit">
              <CalendarDay date={DATE} displayMonth={DATE} />
            </div>
          </RootRangeProvider>
        </Row>
      </Calendar>
    </Col>
  </StoryWrapper>
);
export const SingleDatePicker = () => {
  const [selected, setSelected] = useState<Date | undefined>(DATE);
  return (
    <StoryWrapper col>
      <Calendar.Single selected={selected} onChange={setSelected} />
    </StoryWrapper>
  );
};
export const DateRangePicker = () => {
  const [selected, setSelected] = useState<DateRange | undefined>(DATE_RANGE);
  return (
    <StoryWrapper col>
      <Calendar.Range selected={selected} onChange={setSelected} />
    </StoryWrapper>
  );
};
export const CalendarTheming = () => {
  const [selected, setSelected] = useState<DateRange | undefined>(DATE_RANGE);
  return (
    <StoryWrapper col>
      <Text.ElementHeader>Blue Theme (Default)</Text.ElementHeader>
      <Calendar>
        <Calendar.Range selected={selected} onChange={setSelected} />
      </Calendar>
      <Text.ElementHeader>Green Theme</Text.ElementHeader>
      <Calendar theme="green">
        <Calendar.Range selected={selected} onChange={setSelected} />
      </Calendar>
    </StoryWrapper>
  );
};
zakbutcher commented 3 months ago

@gpbl Just wanted to ping you on my personal account because I was recently let go from my job and would rather any supporting efforts come from my own account. I'm still interested in helping out because this is a great library. If you have any type of roadmap or plan I can check out to orient myself with your goals for v9, I can check them out and figure out a way to start plugging in!

gpbl commented 2 months ago

Hey @zakbutcher, thanks a lot for the follow ups - I'm facing some personal issues myself too, so please forgive my late reply.

I deeply appreciate your offer for help! I will go through your code and comments here in the upcoming days.