mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.88k stars 32.26k forks source link

[Divider] Responsive orientation #29908

Open kevineaton603 opened 2 years ago

kevineaton603 commented 2 years ago

Duplicates

Latest version

Summary 💡

Currently the Divider component can only consume horizontal | vertical.

I would like to see it use the ResponsiveStyleValue as a possible option for the orientation property.

It should work something like this...

<Divider
  orientation={{ xs: 'horizontal', sm: 'vertical' }}
/>

Examples 🌈

No response

Motivation 🔦

I want to use a responsive Divider in conjunction with the Stack component as the Stack component can change directions responsively.

Using the components together would look something like this...

<Stack
  direction={{ xs: 'column', sm: 'row' }}
  alignItems={'center'}
  justifyContent={'center'}
  spacing={2}
  divider={(
    <Divider
      orientation={{ xs: 'horizontal', sm: 'vertical' }}
      flexItem={true}
    />
)}
>
{children}
</Stack>

I think that this features would be very useful not just to me but others developers as they start using the Stack component. I am sure that I won't be the only one to run into these problem in the future.

aleccaputo commented 2 years ago

This would be cool to implement, not seeing many downsides.

dwgr8ergolfer commented 2 years ago

Would love to see this implemented as well.

bjornpijnacker commented 2 years ago

This would be very nice to see indeed. Until then, the snippet below works to achieve similar results

const Component = ({children}) => {
  const theme = useTheme();

  return (
    <Stack
      direction={{ xs: 'column', sm: 'row' }}
      alignItems={'center'}
      justifyContent={'center'}
      spacing={2}
      divider={(
        <Divider
          orientation={useMediaQuery(theme.breakpoints.down("md")) ? "horizontal" : "vertical"}
          flexItem={true}
        />
    )}
    >
    {children}
    </Stack>
  )
}
Zach-Jaensch commented 1 year ago

I have created a wrapper for the Divider using logic similar to the MUI breakpoint utilities(https://github.com/mui/material-ui/issues/29864). This enables me to use the prop exactly like the direction prop. As you can see I have used "react-singleton-hook. If you have a custom theme (specifically custom breakpoints), you must ensure you add <SingletonHooksContainer /> inside the context of your theme provider.

// src/hooks/useCurrentBreakpoint/index.ts

import { useTheme } from "@mui/material";
 import { Breakpoint } from "@mui/system";
 import { useEffect, useState } from "react";
 import { singletonHook } from "react-singleton-hook";

 // https://github.com/Light-Keeper/react-singleton-hook/issues/406#issuecomment-962282765
 // eslint-disable-next-line no-underscore-dangle
 export function _useCurrentBreakpoint(): Breakpoint {
   const globalTheme = useTheme();

   const mqs: [Breakpoint, string][] = globalTheme.breakpoints.keys.map(
     (key, index, breakpoints) => {
       let mq = "";
       if (index === breakpoints.length - 1) {
         mq = globalTheme.breakpoints.up(key);
       } else {
         mq = globalTheme.breakpoints.between(key, breakpoints[index + 1]);
       }
       return [key, mq.replace(/^@media( ?)/m, "")];
     }
   );
   const [currentBreakpoint, setCurrentBreakpoint] = useState<Breakpoint>(() => {
     const bp = mqs.find(([, mq]) => window.matchMedia(mq).matches);
     return bp ? bp[0] : "xs";
   });

   useEffect(() => {
     function handleCurrentBreakpointChange(
       key: Breakpoint,
       e: MediaQueryListEvent
     ) {
       if (e.matches) {
         setCurrentBreakpoint(key);
       }
     }

     const handlers: [string, (e: MediaQueryListEvent) => void][] = mqs.map(
       ([key, mq]) => {
         const handler = (e: MediaQueryListEvent) =>
           handleCurrentBreakpointChange(key, e);
         return [mq, handler];
       }
     );

     handlers.forEach(([mq, handler]) => {
       window.matchMedia(mq).addEventListener("change", handler);
     });

     return () => {
       handlers.forEach(([mq, handler]) => {
         window.matchMedia(mq).removeEventListener("change", handler);
       });
     };
   }, [mqs]);

   return currentBreakpoint;
 }

 const useCurrentBreakpoint = singletonHook("xs", _useCurrentBreakpoint);

 export { useCurrentBreakpoint };
// src/components/ResponsiveDivider/index.tsx

import { useTheme } from "@mui/material";
 import { Breakpoint, ResponsiveStyleValue } from "@mui/system";

 function isBPValueAnObject<T>(
   breakpointValues: ResponsiveStyleValue<T>
 ): breakpointValues is Record<Breakpoint, T | null> {
   return (
     typeof breakpointValues === "object" && !Array.isArray(breakpointValues)
   );
 }

 export function useResolveAllBreakpoints<T>(
   breakpointValues: ResponsiveStyleValue<T>
 ): Record<Breakpoint, T> {
   const bpKeys = useTheme().breakpoints.keys;
   const bpsProvided = (() => {
     if (typeof breakpointValues !== "object") {
       return [];
     }

     let b: Breakpoint[] = [];

     if (Array.isArray(breakpointValues)) {
       b = breakpointValues.map((_, i) => bpKeys[i]);
     } else {
       b = Object.entries(breakpointValues as Record<Breakpoint, T | null>)
         .filter(([k, v]) => bpKeys.includes(k as Breakpoint) && v != null)
         .map(([k]) => k as Breakpoint);
     }
     return b;
   })();
   if (bpsProvided.length === 0) {
     return Object.fromEntries(
       bpKeys.map((k) => [k, breakpointValues as T])
     ) as Record<Breakpoint, T>;
   }

   let previous: Breakpoint | number;

   return bpKeys.reduce(
     (acc: Partial<Record<Breakpoint, unknown>>, breakpoint, i) => {
       if (Array.isArray(breakpointValues)) {
         if (breakpointValues[i] != null) {
           acc[breakpoint] = breakpointValues[i];
           previous = i;
         } else {
           acc[breakpoint] = breakpointValues[previous as number];
         }
       } else if (isBPValueAnObject(breakpointValues)) {
         if (breakpointValues[breakpoint] != null) {
           acc[breakpoint] = breakpointValues[breakpoint];
           previous = breakpoint;
         } else {
           acc[breakpoint] = breakpointValues[previous as Breakpoint];
         }
       } else {
         acc[breakpoint] = breakpointValues;
       }
       return acc;
     },
     {}
   ) as Record<Breakpoint, T>;
 }
// src/hooks/useResolveAllBreakpoints/index.ts

import { Divider, DividerProps } from "@mui/material";
import { ResponsiveStyleValue } from "@mui/system";

import { useCurrentBreakpoint } from "src/hooks/useCurrentBreakpoint";
import { useResolveAllBreakpoints } from "src/hooks/useResolveAllBreakpoints";

export function ResponsiveDivider({
 orientation,
 ...props
}: {
 orientation: ResponsiveStyleValue<"horizontal" | "vertical">;
} & Omit<DividerProps, "orientation">): JSX.Element {
 const currentBreakpoint = useCurrentBreakpoint();

 const bpValues = useResolveAllBreakpoints(orientation);

 const currentOrientation = bpValues[currentBreakpoint];

 return <Divider {...props} orientation={currentOrientation} />;
}
Melancholism commented 1 year ago

Still looking forward to this!

TheBongStack commented 8 months ago

Any updates on this issue? Its still not fixed.

pablojsx commented 7 months ago

waiting for this, also for the calendar ahahhahaha

Linuhusainnk commented 2 months ago

Any updates on this, to have different orientation in different breakpoints

korydondzila commented 1 month ago

How is this not a thing yet?