chore: Moving navigation header to Explorer templates in ADS (#38131)
## Description Moving navigation header to Explorer templates in ADS Fixes [#37614](https://github.com/appsmithorg/appsmith/issues/37614) [#37612](https://github.com/appsmithorg/appsmith/issues/37612) [#37611](https://github.com/appsmithorg/appsmith/issues/37611) ## Automation /ok-to-test tags="@tag.IDE, @tag.Sanity" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/12380719410> > Commit: 440da6dff557348bd4524127c86ecaeee19dfe41 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12380719410&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.IDE, @tag.Sanity` > Spec: > <hr>Tue, 17 Dec 2024 21:00:53 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new `SegmentSwitcher` component for enhanced segment navigation. - Added `EditorSegments` component for rendering a segmented control interface. - **Improvements** - Replaced the `SegmentedHeader` with `SegmentSwitcher` in the explorer pane for improved user experience. - Updated the header component to use the new `NavigationHeader` for better segment management. - Enhanced hover interactions and simplified separator logic within the segmented control. - **Exports** - New exports for `EditorSegments` and `SegmentSwitcher` components to facilitate usage across modules. - Added new documentation for the `EditorSegments` component to provide usage guidelines and examples. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
35a5d83ec7
commit
495f139a83
|
|
@ -55,6 +55,10 @@ export const StyledControlContainer = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&[data-selected="false"]:hover {
|
||||||
|
background-color: var(--ads-v2-color-bg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: var(--ads-v2-border-width-outline) solid
|
outline: var(--ads-v2-border-width-outline) solid
|
||||||
var(--ads-v2-color-outline);
|
var(--ads-v2-color-outline);
|
||||||
|
|
@ -77,7 +81,7 @@ export const StyledControlContainer = styled.div`
|
||||||
|
|
||||||
/* Select all segments which is not a selected and last child */
|
/* Select all segments which is not a selected and last child */
|
||||||
/* seperator */
|
/* seperator */
|
||||||
&:not(:last-child):not([data-selected="true"]):not(
|
&:not(:hover):not(:last-child):not([data-selected="true"]):not(
|
||||||
:has(+ [data-selected="true"])
|
:has(+ [data-selected="true"])
|
||||||
):after {
|
):after {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
@ -87,15 +91,4 @@ export const StyledControlContainer = styled.div`
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: var(--ads-v2-colors-control-field-default-border);
|
background-color: var(--ads-v2-colors-control-field-default-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This before is to mask the separator in left side of selected control */
|
|
||||||
/* Mask the seperator with track background color */
|
|
||||||
&[data-selected="true"]:not(:first-child):after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: -7px;
|
|
||||||
width: 2px;
|
|
||||||
height: 16px;
|
|
||||||
background-color: var(--ads-v2-colors-control-track-default-bg);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Canvas, Meta } from "@storybook/blocks";
|
||||||
|
|
||||||
|
import * as EditorSegmentsStories from "./EditorSegments.stories";
|
||||||
|
|
||||||
|
<Meta of={EditorSegmentsStories} />
|
||||||
|
|
||||||
|
# Editor Segments
|
||||||
|
|
||||||
|
Editor Segments is is built on top of the ADS component - Segmented Control. It is a preset template built for Entity Explorer which has a `max-width of 247px` and has extra padding around each of the segments.
|
||||||
|
|
||||||
|
Editor Segments present a range of options, and should be used when the user can execute those options instantaneously. It can also accept `children` to add more UI controls, if needed. These will be placed on the right side of the segments. It takes `options`, `selectedSegment` and `onSegmentChange` props to handle the value and onChange functionalities of Segmented Control component.
|
||||||
|
|
||||||
|
The options presented should not be binary. If you have a `yes | no` scenario, use a switch, instead.
|
||||||
|
|
||||||
|
## Anatomy
|
||||||
|
|
||||||
|
### Default implementation
|
||||||
|
|
||||||
|
Note: The `options` needs to be passed as required, the UI shown below are not default values being provided to the Editor segments. It also means the `selectedSegment` and `onSegmentChange` props needs to be passed as well.
|
||||||
|
|
||||||
|
<Canvas of={EditorSegmentsStories.EditorSegmentsStory} />
|
||||||
|
|
||||||
|
### More UI controls
|
||||||
|
|
||||||
|
<Canvas of={EditorSegmentsStories.EditorSegmentsStoryWithChildren} />
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React from "react";
|
||||||
|
import { EditorSegments } from "./EditorSegments";
|
||||||
|
import type { EditorSegmentsProps } from "./EditorSegments.types";
|
||||||
|
import type { StoryObj } from "@storybook/react";
|
||||||
|
import { Button } from "../../../Button";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "ADS/Templates/Entity Explorer/Editor Segments",
|
||||||
|
component: EditorSegments,
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/function-component-definition
|
||||||
|
const Template = (args: EditorSegmentsProps) => {
|
||||||
|
return <EditorSegments {...args}>{args.children}</EditorSegments>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorSegmentsStory = Template.bind({}) as StoryObj;
|
||||||
|
EditorSegmentsStory.storyName = "Default";
|
||||||
|
EditorSegmentsStory.args = {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "queries",
|
||||||
|
label: "Queries",
|
||||||
|
startIcon: "queries-v3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "js",
|
||||||
|
label: "JS",
|
||||||
|
startIcon: "content-type-json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ui",
|
||||||
|
label: "UI",
|
||||||
|
startIcon: "dashboard-line",
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: "queries",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorSegmentsStoryWithIcons = Template.bind({}) as StoryObj;
|
||||||
|
EditorSegmentsStoryWithIcons.storyName = "Only Icons";
|
||||||
|
EditorSegmentsStoryWithIcons.args = {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "queries",
|
||||||
|
startIcon: "queries-v3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "js",
|
||||||
|
startIcon: "content-type-json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ui",
|
||||||
|
startIcon: "dashboard-line",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: "queries",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorSegmentsStoryWithLabels = Template.bind({}) as StoryObj;
|
||||||
|
EditorSegmentsStoryWithLabels.storyName = "Only Labels";
|
||||||
|
EditorSegmentsStoryWithLabels.args = {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "queries",
|
||||||
|
label: "Queries",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "js",
|
||||||
|
label: "JS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ui",
|
||||||
|
label: "UI",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: "queries",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorSegmentsStoryWithChildren = Template.bind({}) as StoryObj;
|
||||||
|
EditorSegmentsStoryWithChildren.storyName = "With Children";
|
||||||
|
EditorSegmentsStoryWithChildren.args = {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "queries",
|
||||||
|
label: "Queries",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "js",
|
||||||
|
label: "JS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ui",
|
||||||
|
label: "UI",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: "queries",
|
||||||
|
isFullWidth: true,
|
||||||
|
children: <Button isIconButton kind="secondary" startIcon="plus" />,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Flex } from "../../../Flex";
|
||||||
|
|
||||||
|
export const Container = styled(Flex)`
|
||||||
|
.editor-pane-segment-control {
|
||||||
|
max-width: 247px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
import { SegmentedControl } from "../../../SegmentedControl";
|
||||||
|
import { Container } from "./EditorSegments.styles";
|
||||||
|
import type { EditorSegmentsProps } from "./EditorSegments.types";
|
||||||
|
|
||||||
|
const EditorSegments = (props: EditorSegmentsProps) => {
|
||||||
|
const { children, onSegmentChange, options, selectedSegment } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
alignItems="center"
|
||||||
|
backgroundColor="var(--ads-v2-colors-control-track-default-bg)"
|
||||||
|
className="ide-editor-left-pane__header"
|
||||||
|
gap="spaces-2"
|
||||||
|
justifyContent="space-between"
|
||||||
|
padding="spaces-2"
|
||||||
|
>
|
||||||
|
<SegmentedControl
|
||||||
|
className="editor-pane-segment-control"
|
||||||
|
onChange={onSegmentChange}
|
||||||
|
options={options}
|
||||||
|
value={selectedSegment}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditorSegments.displayName = "EditorSegments";
|
||||||
|
|
||||||
|
EditorSegments.defaultProps = {};
|
||||||
|
|
||||||
|
export { EditorSegments };
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { SegmentedControlOption } from "../../../SegmentedControl";
|
||||||
|
|
||||||
|
export interface EditorSegmentsProps {
|
||||||
|
selectedSegment: string;
|
||||||
|
onSegmentChange: (value: string) => void;
|
||||||
|
options: SegmentedControlOption[];
|
||||||
|
children?: React.ReactNode | React.ReactNode[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./EditorSegments";
|
||||||
|
export * from "./EditorSegments.types";
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { ListItemContainer, ListHeaderContainer } from "./styles";
|
export { ListItemContainer, ListHeaderContainer } from "./styles";
|
||||||
export { ListWithHeader } from "./ListWithHeader";
|
export { ListWithHeader } from "./ListWithHeader";
|
||||||
|
export { EditorSegments } from "./EditorSegments";
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
BUILDER_PATH,
|
BUILDER_PATH,
|
||||||
BUILDER_PATH_DEPRECATED,
|
BUILDER_PATH_DEPRECATED,
|
||||||
} from "ee/constants/routes/appRoutes";
|
} from "ee/constants/routes/appRoutes";
|
||||||
import SegmentedHeader from "./components/SegmentedHeader";
|
import SegmentSwitcher from "./components/SegmentSwitcher";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { getIDEViewMode } from "selectors/ideSelectors";
|
import { getIDEViewMode } from "selectors/ideSelectors";
|
||||||
import { EditorViewMode } from "ee/entities/IDE/constants";
|
import { EditorViewMode } from "ee/entities/IDE/constants";
|
||||||
|
|
@ -41,7 +41,7 @@ const EditorPaneExplorer = () => {
|
||||||
: "100%"
|
: "100%"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SegmentedHeader />
|
<SegmentSwitcher />
|
||||||
<Switch>
|
<Switch>
|
||||||
<SentryRoute
|
<SentryRoute
|
||||||
component={JSExplorer}
|
component={JSExplorer}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
|
||||||
|
import { EditorEntityTab } from "ee/entities/IDE/constants";
|
||||||
|
import { useCurrentEditorState, useSegmentNavigation } from "../../hooks";
|
||||||
|
import { EditorSegments } from "@appsmith/ads";
|
||||||
|
|
||||||
|
const SegmentSwitcher = () => {
|
||||||
|
const { segment } = useCurrentEditorState();
|
||||||
|
const { onSegmentChange } = useSegmentNavigation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorSegments
|
||||||
|
onSegmentChange={onSegmentChange}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: createMessage(EDITOR_PANE_TEXTS.queries_tab),
|
||||||
|
value: EditorEntityTab.QUERIES,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: createMessage(EDITOR_PANE_TEXTS.js_tab),
|
||||||
|
value: EditorEntityTab.JS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: createMessage(EDITOR_PANE_TEXTS.ui_tab),
|
||||||
|
value: EditorEntityTab.UI,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
selectedSegment={segment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SegmentSwitcher;
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Flex, SegmentedControl } from "@appsmith/ads";
|
|
||||||
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
|
|
||||||
import { EditorEntityTab } from "ee/entities/IDE/constants";
|
|
||||||
import { useCurrentEditorState, useSegmentNavigation } from "../../hooks";
|
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
const Container = styled(Flex)`
|
|
||||||
#editor-pane-segment-control {
|
|
||||||
max-width: 247px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SegmentedHeader = () => {
|
|
||||||
const { segment } = useCurrentEditorState();
|
|
||||||
const { onSegmentChange } = useSegmentNavigation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
alignItems="center"
|
|
||||||
backgroundColor="var(--ads-v2-colors-control-track-default-bg)"
|
|
||||||
className="ide-editor-left-pane__header"
|
|
||||||
gap="spaces-2"
|
|
||||||
justifyContent="space-between"
|
|
||||||
padding="spaces-2"
|
|
||||||
>
|
|
||||||
<SegmentedControl
|
|
||||||
id="editor-pane-segment-control"
|
|
||||||
onChange={onSegmentChange}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
label: createMessage(EDITOR_PANE_TEXTS.queries_tab),
|
|
||||||
value: EditorEntityTab.QUERIES,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: createMessage(EDITOR_PANE_TEXTS.js_tab),
|
|
||||||
value: EditorEntityTab.JS,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: createMessage(EDITOR_PANE_TEXTS.ui_tab),
|
|
||||||
value: EditorEntityTab.UI,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={segment}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SegmentedHeader;
|
|
||||||
|
|
@ -27,6 +27,8 @@ import { closeJSActionTab } from "actions/jsActionActions";
|
||||||
import { closeQueryActionTab } from "actions/pluginActionActions";
|
import { closeQueryActionTab } from "actions/pluginActionActions";
|
||||||
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
||||||
import { getCurrentEntityInfo } from "../utils";
|
import { getCurrentEntityInfo } from "../utils";
|
||||||
|
import { useEditorType } from "ee/hooks";
|
||||||
|
import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks";
|
||||||
|
|
||||||
export const useCurrentEditorState = () => {
|
export const useCurrentEditorState = () => {
|
||||||
const [selectedSegment, setSelectedSegment] = useState<EditorEntityTab>(
|
const [selectedSegment, setSelectedSegment] = useState<EditorEntityTab>(
|
||||||
|
|
@ -58,7 +60,9 @@ export const useCurrentEditorState = () => {
|
||||||
export const useSegmentNavigation = (): {
|
export const useSegmentNavigation = (): {
|
||||||
onSegmentChange: (value: string) => void;
|
onSegmentChange: (value: string) => void;
|
||||||
} => {
|
} => {
|
||||||
const basePageId = useSelector(getCurrentBasePageId);
|
const editorType = useEditorType(location.pathname);
|
||||||
|
const { parentEntityId: baseParentEntityId } =
|
||||||
|
useParentEntityInfo(editorType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to handle the segment change
|
* Callback to handle the segment change
|
||||||
|
|
@ -70,17 +74,17 @@ export const useSegmentNavigation = (): {
|
||||||
const onSegmentChange = (value: string) => {
|
const onSegmentChange = (value: string) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case EditorEntityTab.QUERIES:
|
case EditorEntityTab.QUERIES:
|
||||||
history.push(queryListURL({ basePageId }), {
|
history.push(queryListURL({ baseParentEntityId }), {
|
||||||
invokedBy: NavigationMethod.SegmentControl,
|
invokedBy: NavigationMethod.SegmentControl,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EditorEntityTab.JS:
|
case EditorEntityTab.JS:
|
||||||
history.push(jsCollectionListURL({ basePageId }), {
|
history.push(jsCollectionListURL({ baseParentEntityId }), {
|
||||||
invokedBy: NavigationMethod.SegmentControl,
|
invokedBy: NavigationMethod.SegmentControl,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case EditorEntityTab.UI:
|
case EditorEntityTab.UI:
|
||||||
history.push(widgetListURL({ basePageId }), {
|
history.push(widgetListURL({ baseParentEntityId }), {
|
||||||
invokedBy: NavigationMethod.SegmentControl,
|
invokedBy: NavigationMethod.SegmentControl,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user