Skip to content

Commit

Permalink
feat: Initial search UI (feature flagged) (#3371)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Jul 16, 2024
1 parent 84670ae commit 0628d5a
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 9 deletions.
2 changes: 1 addition & 1 deletion editor.planx.uk/src/lib/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// add/edit/remove feature flags in array below
const AVAILABLE_FEATURE_FLAGS = [] as const;
const AVAILABLE_FEATURE_FLAGS = ["SEARCH"] as const;

type FeatureFlag = (typeof AVAILABLE_FEATURE_FLAGS)[number];

Expand Down
134 changes: 134 additions & 0 deletions editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import Typography from "@mui/material/Typography";
import { ComponentType } from "@opensystemslab/planx-core/types";
import { ICONS } from "@planx/components/ui";
import { debounce } from "lodash";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { ChangeEvent, useCallback, useState } from "react";
import useSWR from "swr";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";
import InputLabel from "ui/editor/InputLabel";
import ChecklistItem from "ui/shared/ChecklistItem";
import Input from "ui/shared/Input";

const mockData: SearchResult[] = [
{
nodeId: "abc123",
nodeType: ComponentType.Question,
nodeTitle: "Is the property in Lambeth?",
text: "Lambeth example biodiversity text",
path: ["_root", "xyz123", "abc123"],
},
{
nodeId: "abc456",
nodeType: ComponentType.Notice,
nodeTitle: "It looks like the property is not in Lambeth",
text: "Lambeth example biodiversity text",
path: ["_root", "xyz123", "abc123"],
},
{
nodeId: "abc789",
nodeType: ComponentType.Question,
nodeTitle: "What are you applying about?",
text: "Lambeth example biodiversity text",
path: ["_root", "xyz123", "abc123"],
},
];

export interface SearchResult {
nodeId: string;
nodeType: ComponentType;
nodeTitle?: string;
text: string;
path: string[];
}

const SearchResults: React.FC<{ results: SearchResult[] }> = ({ results }) => {
return (
<Box
sx={{ width: "100%", gap: 2, display: "flex", flexDirection: "column" }}
>
{results.map((result) => (
<SearchResultCard key={result.nodeId} {...result} />
))}
</Box>
);
};

const SearchResultCard: React.FC<SearchResult> = ({
nodeTitle,
text,
nodeType,
}) => {
const Icon = ICONS[nodeType];

return (
<Box
sx={(theme) => ({
pb: 2,
borderBottom: `1px solid ${theme.palette.border.main}`,
})}
>
<Box sx={{ display: "flex", alignItems: "center", mb: 1 }}>
{Icon && <Icon sx={{ mr: 1 }} />}
<Typography
variant="body2"
fontSize={14}
fontWeight={FONT_WEIGHT_SEMI_BOLD}
>
Question
{nodeTitle && ` - ${nodeTitle}`}
</Typography>
</Box>
<Typography variant="body2">{text}</Typography>
</Box>
);
};

const Search: React.FC = () => {
const [flowId] = useStore((state) => [state.id]);
const [query, setQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] = useState(query);

const debounceSearch = useCallback(
debounce((input) => setDebouncedQuery(input), 500),
[],
);

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const input = e.target.value;
setQuery(input);
debounceSearch(input);
};

const fetcher = (url: string) => fetch(url).then((r) => r.json());
const endpoint = `${process.env.REACT_APP_API_URL}/flows/${flowId}/search`;
const { data, error } = useSWR<SearchResult[]>(
debouncedQuery ? `${endpoint}?find=${debouncedQuery}` : null,
fetcher,
);

return (
<Container component={Box} p={3}>
<InputLabel label="Type to search" htmlFor="search">
<Input name="search" onChange={handleChange} id="boundaryUrl" />
</InputLabel>
<ChecklistItem
label="Search only data fields"
id={"search-data-field-facet"}
checked
inputProps={{
disabled: true,
}}
onChange={() => {}}
/>
<Box pt={3}>
{error && "Something went wrong"}
{mockData ? <SearchResults results={mockData} /> : "Loading..."}
</Box>
</Container>
);
};

export default Search;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Tabs from "@mui/material/Tabs";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { AxiosError } from "axios";
import { hasFeatureFlag } from "lib/featureFlags";
import { formatLastPublishMessage } from "pages/FlowEditor/utils";
import React, { useState } from "react";
import { useAsync } from "react-use";
Expand All @@ -34,8 +35,9 @@ import {
ValidationCheck,
ValidationChecks,
} from "./PublishDialog";
import Search from "./Search";

type SidebarTabs = "PreviewBrowser" | "History";
type SidebarTabs = "PreviewBrowser" | "History" | "Search";

const Console = styled(Box)(() => ({
overflow: "auto",
Expand Down Expand Up @@ -449,6 +451,15 @@ const Sidebar: React.FC<{
value="History"
label="History"
/>
{hasFeatureFlag("SEARCH") && (
<StyledTab
disableFocusRipple
disableTouchRipple
disableRipple
value="Search"
label="Search"
/>
)}
</Tabs>
</TabList>
{activeTab === "PreviewBrowser" && (
Expand All @@ -463,6 +474,11 @@ const Sidebar: React.FC<{
</Container>
</SidebarContainer>
)}
{activeTab === "Search" && (
<SidebarContainer py={3}>
<Search />
</SidebarContainer>
)}
{showDebugConsole && <DebugConsole />}
</Root>
);
Expand Down
24 changes: 18 additions & 6 deletions editor.planx.uk/src/ui/shared/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Box from "@mui/material/Box";
import Box, { BoxProps } from "@mui/material/Box";
import { styled } from "@mui/material/styles";
import React from "react";
import { borderedFocusStyle } from "theme";

const Root = styled(Box)(({ theme }) => ({
const Root = styled(Box, {
shouldForwardProp: (prop) => !["disabled"].includes(prop.toString()),
})<BoxProps & { disabled?: boolean }>(({ theme, disabled }) => ({
display: "inline-flex",
flexShrink: 0,
position: "relative",
Expand All @@ -13,6 +15,10 @@ const Root = styled(Box)(({ theme }) => ({
border: "2px solid",
backgroundColor: theme.palette.common.white,
"&:focus-within": borderedFocusStyle,
...(disabled && {
border: `2px solid ${theme.palette.grey[400]}`,
backgroundColor: theme.palette.grey[400],
}),
}));

const Input = styled("input")(() => ({
Expand All @@ -24,11 +30,13 @@ const Input = styled("input")(() => ({

interface IconProps extends React.HTMLAttributes<HTMLSpanElement> {
checked: boolean;
disabled?: boolean;
}

const Icon = styled("span", {
shouldForwardProp: (prop) => prop !== "checked",
})<IconProps>(({ theme, checked }) => ({
shouldForwardProp: (prop) =>
!["checked", "disabled"].includes(prop.toString()),
})<IconProps>(({ theme, checked, disabled }) => ({
display: checked ? "block" : "none",
content: "''",
position: "absolute",
Expand All @@ -41,6 +49,10 @@ const Icon = styled("span", {
top: "42%",
transform: "translate(-50%, -50%) rotate(45deg)",
cursor: "pointer",
...(disabled && {
borderBottom: `5px solid white`,
borderRight: `5px solid white`,
}),
}));

export interface Props {
Expand All @@ -62,15 +74,15 @@ export default function Checkbox({
};

return (
<Root onClick={handleChange}>
<Root onClick={handleChange} disabled={inputProps?.disabled}>
<Input
defaultChecked={checked}
type="checkbox"
id={id}
data-testid={id}
{...inputProps}
/>
<Icon checked={checked} />
<Icon checked={checked} disabled={inputProps?.disabled} />
</Root>
);
}
9 changes: 8 additions & 1 deletion editor.planx.uk/src/ui/shared/ChecklistItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,24 @@ interface Props {
label: string;
checked: boolean;
onChange: (event?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
}

export default function ChecklistItem({
label,
onChange,
checked,
id,
inputProps,
}: Props): FCReturn {
return (
<Root>
<Checkbox checked={checked} id={id} onChange={onChange} />
<Checkbox
checked={checked}
id={id}
onChange={onChange}
inputProps={inputProps}
/>
<Label variant="body1" className="label" component={"label"} htmlFor={id}>
{label}
</Label>
Expand Down

0 comments on commit 0628d5a

Please sign in to comment.