PromucFlow_constructor/app/client/src/pages/Editor/MainContainerLayoutControl.tsx
Preet Sidhu 75cf47b8c5
feat: Auto layout appsmith editor and mobile responsiveness (#21151)
## Description

Core features of Auto Layout and mobile responsiveness, hidden under a feature flag.

> Add a TL;DR when description is extra long (helps content team)



Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video


## Type of change

> Please delete options that are not relevant.


- New feature (non-breaking change which adds functionality)


## How Has This Been Tested?
> Manual regression and sanity tests for all fixed canvas functionality.

- Manual
- Jest
- Cypress



## Checklist:
### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] PR is being merged under a feature flag


---------

Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
Co-authored-by: Arsalan <arsalanyaldram0211@outlook.com>
Co-authored-by: Aswath K <aswath.sana@gmail.com>
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
2023-03-04 12:55:54 +05:30

165 lines
4.7 KiB
TypeScript

import { ReactComponent as DesktopIcon } from "assets/icons/ads/app-icons/monitor-alt.svg";
import { ReactComponent as MultiDeviceIcon } from "assets/icons/ads/app-icons/monitor-smartphone-alt.svg";
import { ReactComponent as MobileIcon } from "assets/icons/ads/app-icons/smartphone-alt.svg";
import { ReactComponent as TabletIcon } from "assets/icons/ads/app-icons/tablet-alt.svg";
import { ReactComponent as TabletLandscapeIcon } from "assets/icons/ads/app-icons/tabletr-alt.svg";
import classNames from "classnames";
import React, { useCallback, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateApplicationLayout } from "actions/applicationActions";
import { IconName, TooltipComponent } from "design-system-old";
import {
AppLayoutConfig,
SupportedLayouts,
} from "reducers/entityReducers/pageListReducer";
import {
getCurrentApplicationId,
getCurrentApplicationLayout,
} from "selectors/editorSelectors";
const IconObj: any = {
FLUID: <MultiDeviceIcon />,
DESKTOP: <DesktopIcon />,
TABLET: <TabletIcon />,
TABLET_LARGE: <TabletLandscapeIcon />,
MOBILE: <MobileIcon />,
};
interface AppsmithLayoutConfigOption {
name: string;
type: SupportedLayouts;
icon?: IconName;
}
export const AppsmithDefaultLayout: AppLayoutConfig = {
type: "FLUID",
};
const AppsmithLayouts: AppsmithLayoutConfigOption[] = [
{
name: "Fluid Width",
type: "FLUID",
icon: "fluid",
},
{
name: "Desktop",
type: "DESKTOP",
icon: "desktop",
},
{
name: "Tablet (Landscape)",
type: "TABLET_LARGE",
icon: "tabletLandscape",
},
{
name: "Tablet (Portrait)",
type: "TABLET",
icon: "tablet",
},
{
name: "Mobile Device",
type: "MOBILE",
icon: "mobile",
},
];
export function MainContainerLayoutControl() {
const dispatch = useDispatch();
const appId = useSelector(getCurrentApplicationId);
const appLayout = useSelector(getCurrentApplicationLayout);
const buttonRefs: Array<HTMLButtonElement | null> = [];
/**
* return selected layout index. if there is no app
* layout, use the default one ( fluid )
*/
const selectedIndex = useMemo(() => {
return AppsmithLayouts.findIndex(
(each) => each.type === (appLayout?.type || AppsmithDefaultLayout.type),
);
}, [appLayout]);
const [focusedIndex, setFocusedIndex] = React.useState(selectedIndex);
/**
* updates the app layout
*
* @param layoutConfig
*/
const updateAppLayout = useCallback(
(layoutConfig: AppLayoutConfig) => {
const { type } = layoutConfig;
dispatch(
updateApplicationLayout(appId || "", {
appLayout: {
type,
},
}),
);
},
[dispatch, appLayout],
);
const handleKeyDown = (event: React.KeyboardEvent, index: number) => {
if (!buttonRefs.length) return;
switch (event.key) {
case "ArrowRight":
case "Right":
const rightIndex = index === buttonRefs.length - 1 ? 0 : index + 1;
buttonRefs[rightIndex]?.focus();
setFocusedIndex(rightIndex);
break;
case "ArrowLeft":
case "Left":
const leftIndex = index === 0 ? buttonRefs.length - 1 : index - 1;
buttonRefs[leftIndex]?.focus();
setFocusedIndex(leftIndex);
break;
}
};
return (
<div className="pb-6 space-y-2 t--layout-control-wrapper">
<div
className="flex justify-around"
onBlur={() => setFocusedIndex(selectedIndex)}
>
{AppsmithLayouts.map((layoutOption: any, index: number) => {
return (
<TooltipComponent
className="flex-grow"
content={layoutOption.name}
key={layoutOption.name}
position={
index === AppsmithLayouts.length - 1 ? "bottom-right" : "bottom"
}
>
<button
className={classNames({
"border-transparent border flex items-center justify-center p-2 flex-grow focus:bg-gray-200": true,
"bg-white border-gray-300": selectedIndex === index,
"bg-gray-100 hover:bg-gray-200": selectedIndex !== index,
})}
onClick={() => {
updateAppLayout(layoutOption);
setFocusedIndex(index);
}}
onKeyDown={(event) => handleKeyDown(event, index)}
ref={(input) => buttonRefs.push(input)}
tabIndex={index === focusedIndex ? 0 : -1}
>
<div style={{ width: "16px", height: "16px" }}>
{IconObj[layoutOption.type]}
</div>
</button>
</TooltipComponent>
);
})}
</div>
</div>
);
}