Skip to content

Commit 8d944ef

Browse files
committed
feat(lightspeed): add chotbot display modes
1 parent 01ae538 commit 8d944ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2888
-1616
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-global-floating-action-button': patch
3+
---
4+
5+
updated drawer classname
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-lightspeed': patch
3+
---
4+
5+
adds lightspeed chatbot popup

workspaces/lightspeed/packages/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@material-ui/icons": "^4.9.1",
4949
"@red-hat-developer-hub/backstage-plugin-global-floating-action-button": "^1.6.1",
5050
"@red-hat-developer-hub/backstage-plugin-lightspeed": "*",
51+
"@red-hat-developer-hub/backstage-plugin-theme": "^0.11.0",
5152
"react": "^18.0.2",
5253
"react-dom": "^18.0.2",
5354
"react-router": "^6.3.0",

workspaces/lightspeed/packages/app/src/App.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ import { RequirePermission } from '@backstage/plugin-permission-react';
5454
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
5555
import { lightspeedTranslations } from '@red-hat-developer-hub/backstage-plugin-lightspeed/alpha';
5656
import { githubAuthApiRef } from '@backstage/core-plugin-api';
57-
import { LightspeedPage } from '@red-hat-developer-hub/backstage-plugin-lightspeed';
58-
import { LightspeedDrawerProvider } from '@red-hat-developer-hub/backstage-plugin-lightspeed';
57+
import {
58+
LightspeedPage,
59+
LightspeedDrawerProvider,
60+
} from '@red-hat-developer-hub/backstage-plugin-lightspeed';
5961

6062
const identityProviders: IdentityProviders = [
6163
'guest',
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {
18+
ComponentType,
19+
useState,
20+
useCallback,
21+
useMemo,
22+
useEffect,
23+
} from 'react';
24+
import { CustomDrawer } from './CustomDrawer';
25+
26+
/**
27+
* Partial drawer state exposed by drawer plugins
28+
*
29+
* @public
30+
*/
31+
export interface DrawerPartialState {
32+
id: string;
33+
isDrawerOpen: boolean;
34+
drawerWidth: number;
35+
setDrawerWidth: (width: number) => void;
36+
}
37+
38+
/**
39+
* Props for drawer state exposer components
40+
*
41+
* @public
42+
*/
43+
export interface DrawerStateExposerProps {
44+
onStateChange: (state: DrawerPartialState) => void;
45+
onUnmount?: (id: string) => void;
46+
}
47+
48+
/**
49+
* Drawer content configuration
50+
*/
51+
type DrawerContentType = {
52+
id: string;
53+
Component: ComponentType<any>;
54+
priority?: number;
55+
};
56+
57+
/**
58+
* State exposer component type
59+
*/
60+
type StateExposerType = {
61+
Component: ComponentType<DrawerStateExposerProps>;
62+
};
63+
64+
export interface ApplicationDrawerProps {
65+
/**
66+
* Array of drawer content configurations
67+
* Maps drawer IDs to their content components
68+
*/
69+
drawerContents: DrawerContentType[];
70+
/**
71+
* Array of state exposer components from drawer plugins
72+
* These are typically mounted via `application/drawer-state` mount point
73+
*
74+
* In RHDH dynamic plugins, this would come from:
75+
* ```yaml
76+
* mountPoints:
77+
* - mountPoint: application/drawer-state
78+
* importName: TestDrawerStateExposer
79+
* ```
80+
*/
81+
stateExposers?: StateExposerType[];
82+
}
83+
84+
export const ApplicationDrawer = ({
85+
drawerContents,
86+
stateExposers = [],
87+
}: ApplicationDrawerProps) => {
88+
// Collect drawer states from all state exposers
89+
const [drawerStates, setDrawerStates] = useState<
90+
Record<string, DrawerPartialState>
91+
>({});
92+
93+
// Callback for state exposers to report their state
94+
const handleStateChange = useCallback((state: DrawerPartialState) => {
95+
setDrawerStates(prev => {
96+
// Only update if something actually changed
97+
const existing = prev[state.id];
98+
if (
99+
existing &&
100+
existing.isDrawerOpen === state.isDrawerOpen &&
101+
existing.drawerWidth === state.drawerWidth &&
102+
existing.setDrawerWidth === state.setDrawerWidth
103+
) {
104+
return prev;
105+
}
106+
return { ...prev, [state.id]: state };
107+
});
108+
}, []);
109+
110+
// Convert states record to array
111+
const statesArray = useMemo(
112+
() => Object.values(drawerStates),
113+
[drawerStates],
114+
);
115+
116+
// Get active drawer - find the open drawer with highest priority
117+
const activeDrawer = useMemo(() => {
118+
return statesArray
119+
.filter(state => state.isDrawerOpen)
120+
.map(state => {
121+
const content = drawerContents.find(c => c.id === state.id);
122+
if (!content) return null;
123+
return { ...state, ...content };
124+
})
125+
.filter(Boolean)
126+
.sort((a, b) => (b?.priority ?? -1) - (a?.priority ?? -1))[0];
127+
}, [statesArray, drawerContents]);
128+
129+
// Manage CSS classes and variables for layout adjustments
130+
useEffect(() => {
131+
if (activeDrawer) {
132+
const className = `docked-drawer-open`;
133+
const cssVar = `--docked-drawer-width`;
134+
135+
document.body.classList.add(className);
136+
document.body.style.setProperty(cssVar, `${activeDrawer.drawerWidth}px`);
137+
138+
return () => {
139+
document.body.classList.remove(className);
140+
document.body.style.removeProperty(cssVar);
141+
};
142+
}
143+
return undefined;
144+
// eslint-disable-next-line react-hooks/exhaustive-deps
145+
}, [activeDrawer?.id, activeDrawer?.drawerWidth]);
146+
147+
// Wrapper to handle the width change callback type
148+
const handleWidthChange = useCallback(
149+
(width: number) => {
150+
activeDrawer?.setDrawerWidth(width);
151+
},
152+
[activeDrawer],
153+
);
154+
155+
return (
156+
<>
157+
{/* Render all state exposers - they return null but report their state */}
158+
{stateExposers.map(({ Component }, index) => (
159+
<Component
160+
key={`${index}-${Component.displayName}`}
161+
onStateChange={handleStateChange}
162+
/>
163+
))}
164+
165+
{/* Render the active drawer */}
166+
{activeDrawer && (
167+
<CustomDrawer
168+
isDrawerOpen
169+
drawerWidth={activeDrawer.drawerWidth}
170+
onWidthChange={handleWidthChange}
171+
>
172+
<activeDrawer.Component />
173+
</CustomDrawer>
174+
)}
175+
</>
176+
);
177+
};
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/* eslint-disable no-restricted-imports */
18+
import Box from '@mui/material/Box';
19+
import Drawer from '@mui/material/Drawer';
20+
/* eslint-enable no-restricted-imports */
21+
22+
import { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme';
23+
24+
export type CustomDrawerProps = {
25+
children: React.ReactNode;
26+
minWidth?: number;
27+
maxWidth?: number;
28+
initialWidth?: number;
29+
isDrawerOpen: boolean;
30+
drawerWidth?: number;
31+
onWidthChange?: (width: number) => void;
32+
[key: string]: any;
33+
};
34+
35+
export const CustomDrawer = (props: CustomDrawerProps) => {
36+
const {
37+
children,
38+
minWidth = 400,
39+
maxWidth = 800,
40+
initialWidth = 400,
41+
isDrawerOpen,
42+
drawerWidth,
43+
onWidthChange,
44+
...drawerProps
45+
} = props;
46+
47+
// Ensure anchor is always 'right' and not overridden by drawerProps
48+
const { anchor: _, ...restDrawerProps } = drawerProps;
49+
50+
return (
51+
<Drawer
52+
{...restDrawerProps}
53+
anchor="right"
54+
sx={{
55+
'& .v5-MuiDrawer-paper': {
56+
width: drawerWidth || initialWidth,
57+
boxSizing: 'border-box',
58+
backgroundColor: theme => {
59+
const themeConfig = theme as ThemeConfig;
60+
return (
61+
themeConfig.palette?.rhdh?.general?.sidebarBackgroundColor ||
62+
theme.palette.background.paper
63+
);
64+
},
65+
justifyContent: 'space-between',
66+
},
67+
// Only apply header offset when global header exists
68+
'body:has(#global-header) &': {
69+
'& .v5-MuiDrawer-paper': {
70+
top: '64px !important',
71+
height: 'calc(100vh - 64px) !important',
72+
},
73+
},
74+
}}
75+
variant="persistent"
76+
open={isDrawerOpen}
77+
>
78+
<Box sx={{ height: '100%', position: 'relative' }}>{children}</Box>
79+
</Drawer>
80+
);
81+
};

0 commit comments

Comments
 (0)