mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
3.92k stars 1.2k forks source link

[DatePicker] Customize day headers #4605

Closed gReis89 closed 1 year ago

gReis89 commented 4 years ago

I'm trying to customize the DatePicker designs as close as possible to my requirements. We are using 3 letters(mon - tue - wed...) to describe our week days but I can't see any option where I can customize the default headers(M - T - W...).

Screen Shot 2020-05-22 at 19 09 45

dmtrKovalenko commented 3 years ago

It is possible using date-io adapter inheritance. Here is a guide https://next.material-ui-pickers.dev/guides/date-io-customization#override-date-logic

You need to extend getWeekdays method and simply return an array with 7 strings. But make sure that your weekdays will be properly displayed for different locales 😉

oliviertassinari commented 3 years ago

Do we have a demo for it? What about we add one?

I have included the concern in https://github.com/mui-org/material-ui/issues/20960.

tpeek commented 3 years ago

@dmtrKovalenko Unfortunately, it looks like the behavior for showing just one character for the weekday is hardcoded:

https://github.com/mui-org/material-ui/blob/9b1f6f72b51a72910a5dff1b47b9d351629bda51/packages/material-ui-lab/src/DayPicker/PickersCalendar.tsx#L180 It doesn't matter what getWeekdays returns, the date picker will always show just the first letter.

oliviertassinari commented 3 years ago

@tpeek Thanks for reporting the issue. What I have found during my investigation:

two-letter weekday abbreviations. The purpose of these is for things like calendar pickers, thus they should be as small as possible.

var date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
console.log(new Intl.DateTimeFormat('fr-FR', { weekday: 'short' }).format(date)); // 'dim.'
marthaerm commented 3 years ago

Hi! @oliviertassinari is this issue good to take? I would like to give it a try :) This would be my first contribution.

oliviertassinari commented 3 years ago

@marthaerm Mind that this issue isn't straightforward. We recommend issues with the "good first issue" label to get started https://github.com/mui-org/material-ui/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22

brorlarsnicklas commented 3 years ago

Hi, is anyone working on this at the moment? If not, what is the intended approach right now? I'd like to work on it if you can guide me in the direction that you would want.

oliviertassinari commented 3 years ago

@brorlarsnicklas Nobody working on it. I think that resolving this issue will involve internalizing date-io in here. I also think that it involves reconsidering what's the best customization API. It's a large effort.

brorlarsnicklas commented 3 years ago

@oliviertassinari oh, I see. Then it might be better for someone else to work with it hehe! :D

yevheniiminin commented 2 years ago

@oliviertassinari any chance that you could somehow allow to overwrite this behavior? It's the only thing that's blocking us from updating to v5

HossamHoussien commented 2 years ago

If you're lazy to read through, just jump this code snippet to see a complete working snippet

I encourage you to read the following...

For anyone struggling to get this done as I did. I came to a hacky workaround to overcome this issue.

Since the component is set in stone to always get the first character from the provided weekdays given by the date-io adapter https://github.com/mui-org/material-ui/blob/9b1f6f72b51a72910a5dff1b47b9d351629bda51/packages/material-ui-lab/src/DayPicker/PickersCalendar.tsx#L173-L182

Note

My hack to get it done: By extending the Array.prototype.chartAt to return the first "element" so all you need to do is to add this line in your codebase*

Array.prototype.charAt = function(index) {
  return this[index];
};

Then by updating the used locale (or providing a custom locale if you need more customization) to override the weekdaysShort labels, for example, I'm using moment here.

moment.updateLocale('en-gb', {

  // Default value from moment
  // weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),

  // New value
  weekdaysShort: [["Sun"], ["Mon"], ["Tue"], ["Wed"], ["Thu"], ["Fri"], ["Sat"]]
});

A complete working snippet can be found here https://codesandbox.io/s/nervous-pike-rv9sn?file=/src/App.js

Note

HossamHoussien commented 2 years ago

Another approach

Another solution inspired by @dmtrKovalenko's comment above

Simply, provide a custom DateAdapter with a little tweak to it to make it work with the current implementation of Mui DatePickers.

// CustomDateAdapter.js
import DateIOAdapter from "@mui/lab/AdapterMoment";

export default function CustomAdapter(options) {
  const adapter = new DateIOAdapter(options);

  const constructDayObject = (day) => ({ charAt: () => day });

  return {
    ...adapter,

    getWeekdays() {
      // Feel free to replace this with your custom value
      // e.g const customWeekdays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
      const customWeekdays = adapter.getWeekdays();

      return customWeekdays.map((day) => constructDayObject(day));
    }
  };
}
// Your cool component

import React from "react";
import moment from "moment";
import StaticDatePicker from "@mui/lab/StaticDatePicker";
import LocalizationProvider from "@mui/lab/LocalizationProvider";

import CustomDateAdapter from "./CustomDateAdapter";

export default function index() {
  return (
    <LocalizationProvider dateAdapter={CustomDateAdapter}>
      {/* This can be replaced by any other DatePicker component */}
      <StaticDatePicker
        openTo="day"
        showToolbar={false}
        displayStaticWrapperAs="desktop"
        value={moment()}
        onChange={() => {}}
        renderInput={(params) => <input {...params} />}
      />
    </LocalizationProvider>
  );
}

I prefer this solution over my previous one as it won't mess up the native objects as Array. You can find a complete working snippet here https://codesandbox.io/s/twilight-sky-t7dto?file=/src/App.js

zastrich commented 2 years ago

Beautiful hack, i implement another step to showing weekdays without UPPERCASE, much more elegant.

import DateIOAdapter from "@mui/lab/AdapterMoment";

export default function CustomDateAdapter(options) {
  const adapter = new DateIOAdapter(options);

  const constructUpperObject = (text) => ({ toUpperCase: () => text });
  const constructDayObject = (day) => ({ charAt: () => constructUpperObject(day) });

  return {
    ...adapter,

    getWeekdays() {
      // Feel free to replace this with your custom value
      // e.g const customWeekdays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
      const customWeekdays = adapter.getWeekdays();

      return customWeekdays.map((day) => constructDayObject(day));
    }
  };
}

See snippet here

avs-git commented 2 years ago

Another solution inspired by @HossamHoussien comment for TypeScript.

import AdapterDateFns from '@mui/lab/AdapterDateFns';

class CustomString extends String {
  charAt(_: number): string {
    return this.valueOf();
  }
}

const weekDays = ['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'];
const customWeekDays = weekDays.map((day) => new CustomString(day) as string);

export class DateAdapter extends AdapterDateFns {
  getWeekdays = (): string[] => customWeekDays;
}

This solution is safety for using with TypeScript and needs almost no casting. But it needs some changing in CSS to avoid toUppercase effect.

ghm-salvarado commented 2 years ago

Thanks @avs-git, that works well. You can avoid the toUppercase effect here too instead of messing with CSS, but it requires using any (unless there's something I'm missing):

class CustomString extends String {
  charAt(_: number): any {
    return {
      toUpperCase: () => this.valueOf()
    }
  }
}
maapteh commented 2 years ago

So many hacks needed for something so small. So when the adapter/lib tells me a short weekDay is Ma. then the component just thinks M is better to display? I really hope you can fix it in the core

vshjxyz commented 2 years ago

Hello, I've been trying to edit the headers as well by extending the adapter as suggested, however, this line prevents any edit as it always forces the picker to use the first character of the day name to uppercase

https://github.com/mui/mui-x/blob/3f9bc694443d8396a97c74d87d8ade6b8f60eeb1/packages/x-date-pickers/src/CalendarPicker/DayPicker.tsx#L154-L158

I know this would be a breaking change, but I really think the .charAt(0).toUpperCase() should be removed. Alternatively, if we know that the extender is not the default one (tricky part), then we can avoid that formatting and leave it "As-is"

ozgurbaserdem commented 2 years ago

Hey ghm-salvarado! Thank you for this work around, it does look smooth but when we run it in our app we get the following TypeError: Uncaught TypeError: String.prototype.valueOf requires that 'this' be a String at CustomString.valueOf Has anyone else came across the same issue with this work around?

As a clarification this occurs when we use:

class CustomString extends String {
  charAt(_: number): any {
    return {
      toUpperCase: () => this.valueOf()
    }
  }
}
const weekDaysSwe = ["Mån", "Tis", "Ons", "Tors", "Fre", "Lör", "Sön"];
const customWeekDaysSwe = weekDaysSwe.map(
  (day) => new CustomString(day) as string
);

export class SvLocalizationUtil extends AdapterDateFns {
  getWeekdays = (): string[] => customWeekDaysSwe;
}

I tried to remove the any prop, and the toUpperCase function as well and use avs-git solution as well:

class CustomString extends String {
  charAt(_: number): string {
    return this.valueOf();
  }
}
const weekDaysSwe = ["Mån", "Tis", "Ons", "Tors", "Fre", "Lör", "Sön"];
const customWeekDaysSwe = weekDaysSwe.map(
  (day) => new CustomString(day) as string
);

export class SvLocalizationUtil extends AdapterDateFns {
  getWeekdays = (): string[] => customWeekDaysSwe;
}

But the same TypeError issue persists.

Should also mention that I use: import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; and not import AdapterDateFns from '@mui/lab/AdapterDateFns';

nakul952 commented 1 year ago

I'm trying to customize the DatePicker designs as close as possible to my requirements. We are using 3 letters(mon - tue - wed...) to describe our week days but I can't see any option where I can customize the default headers(M - T - W...).

Screen Shot 2020-05-22 at 19 09 45

now I'm facing the same issue on my react project but I have found a solution it would help if you used this prop dayOfWeekFormatter={(day) => ${day}} Screenshot 2023-06-26 120049

fernandoem88 commented 3 months ago

if someone else has this issue: my solution is to use a ref that goes from 0 - 7 modulo.


const weekdayRef = useRef(0);

          const dayOfWeekFormatter = (firstDayLetter: string) => {
          const weekday = weekdayRef.current + 1;
          weekdayRef.current = weekday % 7;
          return <Box>{weekday}</Box>
}
// every time the component re-renders, the ref restarts from 0 to 7
LukasTy commented 3 months ago

@fernandoem88 Which version of @mui/x-date-pickers are you using? On v6.16.0 we have introduced a second argument to the dayOfWeekFormatter, if you are using that version or above, consider refactoring your code to avoid the need for useRef. 😉 P.S. On v7 we have ditched the first string argument and kept only the date object.

joaccocaballero commented 2 months ago

How can I change color of day headers? using Slot props in datepicker component

LukasTy commented 2 months ago

How can I change color of day headers? using Slot props in datepicker component

Hey @joaccocaballero, have you checked this playground? 🤔 Try selecting the DayCalendar component and the weekDayLabel slot.