Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Autocomplete } from "@mui/material";

export const StyledAutocomplete = styled(Autocomplete)`
&.MuiAutocomplete-root {
grid-column: 1 / -1;

.MuiOutlinedInput-root {
padding: 0 12px;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import styled from "@emotion/styled";
import { bpUpMd } from "../../../../../../styles/common/mixins/breakpoints";

/**
* Container for sidebar tools, including the mode toggle and filter controls.
* @deprecated - This styled component is deprecated and will be removed in a future release.
*/
export const SidebarTools = styled.div`
display: grid;
gap: 8px 0;
Expand Down
9 changes: 9 additions & 0 deletions src/components/common/ToggleButtonGroup/provider/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";
import { ToggleButtonGroupContextProps } from "./types";

export const ToggleButtonGroupContext = createContext<
ToggleButtonGroupContextProps<unknown>
>({
onChange: undefined,
value: null,
});
16 changes: 16 additions & 0 deletions src/components/common/ToggleButtonGroup/provider/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ToggleButtonGroupProps } from "@mui/material";
import { useContext } from "react";
import { ToggleButtonGroupContext } from "./context";
import { ToggleButtonGroupContextProps } from "./types";

/**
* Returns the toggle button group context.
* @returns toggle button group context.
*/
export const useToggleButtonGroup = <
T extends ToggleButtonGroupProps["value"],
>(): ToggleButtonGroupContextProps<T> => {
return useContext(
ToggleButtonGroupContext,
) as ToggleButtonGroupContextProps<T>;
};
34 changes: 34 additions & 0 deletions src/components/common/ToggleButtonGroup/provider/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ToggleButtonGroupProps } from "@mui/material";
import { JSX, MouseEvent, useCallback, useState } from "react";
import { ToggleButtonGroupContext } from "./context";
import { ToggleButtonGroupProviderProps } from "./types";

/**
* ToggleButtonGroup provider component.
* Manages toggle button group state for child components.
* @param props - Component props.
* @param props.children - Child elements to render.
* @param props.initialValue - Initial value for the toggle button group.
* @returns ToggleButtonGroup provider component.
*/
export function ToggleButtonGroupProvider<
T extends ToggleButtonGroupProps["value"],
>({
children,
initialValue = null,
}: ToggleButtonGroupProviderProps<T>): JSX.Element {
const [value, setValue] = useState<T | null>(initialValue);

const onChange = useCallback(
(_: MouseEvent<HTMLElement>, value: T) => setValue(value),
[],
);

return (
<ToggleButtonGroupContext.Provider value={{ onChange, value }}>
{typeof children === "function"
? children({ onChange, value })
: children}
</ToggleButtonGroupContext.Provider>
);
}
17 changes: 17 additions & 0 deletions src/components/common/ToggleButtonGroup/provider/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ToggleButtonGroupProps } from "@mui/material";
import { ReactNode } from "react";

export type ToggleButtonGroupContextProps<
T extends ToggleButtonGroupProps["value"],
> = Pick<ToggleButtonGroupProps, "onChange"> & {
value: T | null;
};

export type ToggleButtonGroupProviderProps<
T extends ToggleButtonGroupProps["value"],
> = {
children:
| ReactNode
| ((props: ToggleButtonGroupContextProps<T>) => ReactNode);
initialValue?: T | null;
};
8 changes: 8 additions & 0 deletions src/config/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ import { ExploreState } from "../providers/exploreState";
import { FileManifestState } from "../providers/fileManifestState";
import { SystemStatus, SystemStatusResponse } from "../providers/systemStatus";

/**
* AI configuration.
*/
export interface AiConfig {
enabled: boolean;
}

/**
* Interface to define the analytics configuration for a given site.
*/
Expand Down Expand Up @@ -364,6 +371,7 @@ interface TrackingConfig {
* Interface that will hold the whole configuration for a given site.
*/
export interface SiteConfig {
ai?: AiConfig;
analytics?: AnalyticsConfig;
appTitle: string;
authentication?: AuthenticationConfig;
Expand Down
1 change: 1 addition & 0 deletions src/tests/testIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const TEST_IDS = {
FILTER_SORT_MENU: "filter-sort-menu",
FILTER_TERM: "filter-term",
SEARCH_ALL_FILTERS: "search-all-filters",
SEARCH_CONTROLS: "search-controls",
SIDEBAR: "sidebar",
SIDEBAR_DRAWER: "sidebar-drawer",
TABLE_FIRST_CELL: "table-first-cell",
Expand Down
83 changes: 49 additions & 34 deletions src/views/ExploreView/exploreView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { SearchAllFilters } from "../../components/Filter/components/SearchAllFi
import { SURFACE_TYPE } from "../../components/Filter/components/surfaces/types";
import { Index as IndexView } from "../../components/Index/index";
import { SidebarLabel } from "../../components/Layout/components/Sidebar/components/SidebarLabel/sidebarLabel";
import { SidebarTools } from "../../components/Layout/components/Sidebar/components/SidebarTools/sidebarTools.styles";
import { Sidebar } from "../../components/Layout/components/Sidebar/sidebar";
import { CategoryGroup } from "../../config/entities";
import { useStateSyncManager } from "../../hooks/stateSyncManager/hook";
Expand All @@ -32,6 +31,10 @@ import { SELECT_CATEGORY_KEY } from "../../providers/exploreState/constants";
import { TEST_IDS } from "../../tests/testIds";
import { useUpdateFilterSort } from "./hooks/UseUpdateFilterSort/hook";
import { buildStateSyncManagerContext } from "./utils";
import { ModeProvider } from "./mode/provider/provider";
import { ToggleButtonGroup } from "./mode/components/ToggleButtonGroup/toggleButtonGroup";
import { StyledGrid } from "./search/filters/filters.styles";
import { StyledStack } from "./search/sidebar/sidebar.styles";

export interface ExploreViewProps extends AzulEntitiesStaticResponse {
className?: string;
Expand Down Expand Up @@ -134,43 +137,55 @@ export const ExploreView = (props: ExploreViewProps): JSX.Element => {
}, [entityListType, exploreDispatch]);

return (
<DrawerProvider>
{categoryViews && !!categoryViews.length && (
<Sidebar>
<SidebarTools data-testid={TEST_IDS.FILTER_CONTROLS}>
<SidebarLabel label={"Filters"} />
<Stack direction="row" gap={4}>
<ClearAllFilters />
<FilterSort
enabled={filterSortEnabled}
filterSort={filterSort}
onFilterSortChange={onFilterSortChange}
<ModeProvider>
{(modeProps) => (
<DrawerProvider>
{categoryViews && !!categoryViews.length && (
<Sidebar>
<StyledStack data-testid={TEST_IDS.SEARCH_CONTROLS} useFlexGap>
<ToggleButtonGroup
onChange={modeProps.onChange}
value={modeProps.value}
/>
<StyledGrid data-testid={TEST_IDS.FILTER_CONTROLS}>
<SidebarLabel label={"Filters"} />
<Stack direction="row" gap={4}>
<ClearAllFilters />
<FilterSort
enabled={filterSortEnabled}
filterSort={filterSort}
onFilterSortChange={onFilterSortChange}
/>
</Stack>
<SearchAllFilters
categoryViews={categoryViews}
onFilter={onFilterChange.bind(null, true)}
surfaceType={
mdDown
? SURFACE_TYPE.POPPER_DRAWER
: SURFACE_TYPE.POPPER_MENU
}
/>
</StyledGrid>
</StyledStack>
<Filters
categoryFilters={categoryFilters}
onFilter={onFilterChange.bind(null, false)}
surfaceType={mdDown ? SURFACE_TYPE.DRAWER : SURFACE_TYPE.MENU}
trackFilterOpened={trackingConfig?.trackFilterOpened}
/>
</Stack>
<SearchAllFilters
categoryViews={categoryViews}
onFilter={onFilterChange.bind(null, true)}
surfaceType={
mdDown ? SURFACE_TYPE.POPPER_DRAWER : SURFACE_TYPE.POPPER_MENU
}
/>
</SidebarTools>
<Filters
</Sidebar>
)}
<IndexView
className={props.className}
categoryFilters={categoryFilters}
onFilter={onFilterChange.bind(null, false)}
surfaceType={mdDown ? SURFACE_TYPE.DRAWER : SURFACE_TYPE.MENU}
trackFilterOpened={trackingConfig?.trackFilterOpened}
entityListType={entityListType}
entityName={label}
loading={loading}
/>
</Sidebar>
</DrawerProvider>
)}
<IndexView
className={props.className}
categoryFilters={categoryFilters}
entityListType={entityListType}
entityName={label}
loading={loading}
/>
</DrawerProvider>
</ModeProvider>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ToggleButtonOwnProps, ToggleButtonGroupProps } from "@mui/material";
import { MODE } from "../../types";

export const TOGGLE_BUTTON_GROUP_PROPS: ToggleButtonGroupProps = {
exclusive: true,
fullWidth: true,
};

export const TOGGLE_BUTTONS: (Omit<ToggleButtonOwnProps, "value"> & {
value: MODE;
})[] = [
// `disabled` and `selected` are temporary until we have the mode context fully implemented and can set the value based on that context.
{ children: "Research", disabled: true, value: MODE.RESEARCH },
{ children: "Search", selected: true, value: MODE.SEARCH },
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import { ToggleButtonGroup } from "../toggleButtonGroup";
import { MODE } from "../../../types";
import { fn } from "storybook/test";

const meta: Meta<typeof ToggleButtonGroup> = {
component: ToggleButtonGroup,
};

export default meta;

type Story = StoryObj<typeof meta>;

export const DEFAULT: Story = {
args: {
onChange: fn(),
value: MODE.SEARCH,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from "@emotion/styled";
import { ToggleButtonGroup } from "@mui/material";
import { PALETTE } from "../../../../../styles/common/constants/palette";

export const StyledToggleButtonGroup = styled(ToggleButtonGroup)`
.MuiToggleButton-root {
padding: 6px 16px;
text-transform: none;

&.Mui-disabled {
color: ${PALETTE.INK_LIGHT};
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ToggleButton, ToggleButtonGroupProps } from "@mui/material";
import { JSX } from "react";
import { TOGGLE_BUTTON_GROUP_PROPS, TOGGLE_BUTTONS } from "./constants";
import { StyledToggleButtonGroup } from "./toggleButtonGroup.styles";

export const ToggleButtonGroup = ({
className,
...props /* ToggleButtonGroupProps */
}: ToggleButtonGroupProps): JSX.Element | null => {
if (!props.onChange) return null;
return (
<StyledToggleButtonGroup
className={className}
{...TOGGLE_BUTTON_GROUP_PROPS}
{...props}
>
{TOGGLE_BUTTONS.map((tButtonProps) => (
<ToggleButton key={tButtonProps.value} {...tButtonProps} />
))}
</StyledToggleButtonGroup>
);
};
3 changes: 3 additions & 0 deletions src/views/ExploreView/mode/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const FEATURE_FLAG = {
CHAT: "chat",
} as const;
4 changes: 4 additions & 0 deletions src/views/ExploreView/mode/provider/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createContext } from "react";
import { ModeContextProps } from "./types";

export const ModeContext = createContext<ModeContextProps>({});
11 changes: 11 additions & 0 deletions src/views/ExploreView/mode/provider/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useContext } from "react";
import { ModeContext } from "./context";
import { ModeContextProps } from "./types";

/**
* Returns the mode context.
* @returns Mode context with value and onChange, or empty object if feature disabled.
*/
export const useMode = (): ModeContextProps => {
return useContext(ModeContext);
};
34 changes: 34 additions & 0 deletions src/views/ExploreView/mode/provider/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { JSX } from "react";
import { ToggleButtonGroupProvider } from "../../../../components/common/ToggleButtonGroup/provider/provider";
import { useConfig } from "../../../../hooks/useConfig";
import { useFeatureFlag } from "../../../../hooks/useFeatureFlag/useFeatureFlag";
import { FEATURE_FLAG } from "../constants";
import { MODE } from "../types";
import { ModeContext } from "./context";
import { ModeProviderProps } from "./types";

/**
* Mode provider component "search" or "research" i.e. self-directed search vs. chat-based search.
* Either mode is available based on the "chat" feature flag, or by configuration.
* Wraps children with mode context based on whether the feature is enabled or not.
* @param props - Component props.
* @param props.children - Children.
* @returns Mode provider component.
*/
export function ModeProvider({ children }: ModeProviderProps): JSX.Element {
const flagEnabled = useFeatureFlag(FEATURE_FLAG.CHAT);
const { config } = useConfig();
const { ai } = config;
const enabled = flagEnabled || ai?.enabled;
return (
<ToggleButtonGroupProvider<MODE> initialValue={MODE.SEARCH}>
{(props) => (
<ModeContext.Provider value={enabled ? props : {}}>
{typeof children === "function"
? children(enabled ? props : {})
: children}
</ModeContext.Provider>
)}
</ToggleButtonGroupProvider>
);
}
9 changes: 9 additions & 0 deletions src/views/ExploreView/mode/provider/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from "react";
import { ToggleButtonGroupContextProps } from "../../../../components/common/ToggleButtonGroup/provider/types";
import { MODE } from "../types";

export type ModeContextProps = Partial<ToggleButtonGroupContextProps<MODE>>;

export type ModeProviderProps = {
children: ReactNode | ((props: ModeContextProps) => ReactNode);
};
4 changes: 4 additions & 0 deletions src/views/ExploreView/mode/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum MODE {
RESEARCH = "RESEARCH",
SEARCH = "SEARCH",
}
7 changes: 7 additions & 0 deletions src/views/ExploreView/search/filters/filters.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from "@emotion/styled";

export const StyledGrid = styled.div`
display: grid;
gap: 8px 16px;
grid-template-columns: 1fr auto;
`;
Loading