Skip to content

Commit 2dc856e

Browse files
authored
style: revamp tag config panel (#101)
1 parent 3cf272d commit 2dc856e

11 files changed

Lines changed: 189 additions & 14 deletions

File tree

src/pages/sidepanel/SidePanel.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,15 @@ const SidePanel: React.FC<SidePanelProps> = ({ key, isEnabled }) => {
104104
</Box>
105105
) : (
106106
<>
107-
<Box justifyContent={'center'} alignItems={'flex-start'} display="flex" flexDirection="column">
107+
<Box
108+
justifyContent={'center'}
109+
alignItems={'flex-start'}
110+
display="flex"
111+
flexDirection="column"
112+
sx={{
113+
height: 'calc(100vh - 160px)', // Full height minus top navigation (~90px) and bottom navigation (70px)
114+
overflow: 'hidden',
115+
}}>
108116
<Routes>
109117
<Route path="/settings" element={<Configuration />} />
110118
<Route

src/pages/sidepanel/components/BottomNavigation.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ const StyledBottomNavigation = styled(BottomNavigation)(({ theme }) => ({
2020
position: 'fixed',
2121
bottom: 0,
2222
width: '100%',
23-
height: 'auto',
23+
height: '4.375rem', // 70px (Figma spec)
2424
display: 'flex',
2525
justifyContent: 'space-between',
26-
padding: theme.spacing(1.5, 2),
26+
alignItems: 'center',
27+
gap: '0.625rem', // 10px (Figma spec)
28+
padding: '0.75rem 1rem', // 12px top/bottom, 16px left/right (Figma spec)
29+
backgroundColor: theme.palette.background.default,
30+
backdropFilter: 'blur(13.3px)', // 13.3px - Figma blur value
31+
WebkitBackdropFilter: 'blur(13.3px)', // Safari support
2732
cursor: 'default',
2833
transition: 'none',
2934
'&:hover': {

src/pages/sidepanel/components/CustomTabPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ interface TabPanelProps {
99
}
1010

1111
const TabPanelContainer = styled(Box)(({ theme }) => ({
12-
padding: theme.spacing(1),
13-
height: 'calc(100vh - 185px)',
14-
overflow: 'auto',
12+
padding: theme.spacing(1.25), // 10px padding
13+
paddingBottom: theme.spacing(12), // 96px bottom space to clear bottom navigation + buttons
14+
boxSizing: 'border-box',
1515
}));
1616

1717
const CustomTabPanel = (props: TabPanelProps): JSX.Element => {

src/pages/sidepanel/sections/Debugger.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ const Debugger: React.FC<DebuggerProps> = ({ tagIsInstalled, tagConfig, getter,
2525
];
2626

2727
return (
28-
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100%'} width={'100%'}>
28+
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
2929
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
30-
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
30+
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
3131
<CustomTabPanel value={getter} index={0}>
3232
<TagConfig tagConfig={tagConfig} />
3333
</CustomTabPanel>

src/pages/sidepanel/sections/Personalization.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ const Personalization: React.FC<PersonalizationProps> = ({ candidates, getter, s
5353
];
5454

5555
return (
56-
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100%'} width={'100%'}>
56+
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
5757
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
58-
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
58+
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
5959
<CustomTabPanel value={getter} index={0}>
6060
{candidates?.experiences.length > 0 ? (
6161
<TabDetails items={candidates?.experiences} />

src/pages/sidepanel/sections/Profile.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ const Profile: React.FC<ProfileTabProps> = ({ profileIsLoading, profile, getter,
2424
];
2525

2626
return (
27-
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100vh'} width={'100%'} overflow={'hidden'}>
27+
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
2828
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
29-
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
29+
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
3030
<CustomTabPanel value={getter} index={0}>
3131
{profileIsLoading ? (
3232
<Box m={2}>
Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,64 @@
11
import React from 'react';
2+
import { Box, Button, Stack } from '@mui/material';
3+
import { styled } from '@mui/material/styles';
4+
import { FileDownload } from '@mui/icons-material';
5+
import { appColors } from '@root/src/theme/palette';
26
import TreeDisplay from '@root/src/pages/sidepanel/components/TreeDisplay';
37

48
interface ProfileDetailTabProps {
59
profile: any;
610
}
711

12+
const StyledPanel = styled(Box)(({ theme }) => ({
13+
width: '100%',
14+
backgroundColor: appColors.common.darkPanel,
15+
borderRadius: theme.spacing(1), // 8px all corners
16+
padding: '0.625rem',
17+
cursor: 'default',
18+
transition: 'none',
19+
'&:hover': {
20+
boxShadow: 'none',
21+
transform: 'none',
22+
},
23+
}));
24+
25+
const ExportButton = styled(Button)(({ theme }) => ({
26+
width: '100%',
27+
padding: theme.spacing(1.5),
28+
backgroundColor: appColors.common.white,
29+
color: appColors.neutral[900],
30+
fontWeight: appColors.common.fontWeight.semiBold,
31+
fontSize: appColors.common.fontSize.base,
32+
textTransform: 'none',
33+
borderRadius: theme.spacing(1),
34+
border: `1px solid ${appColors.neutral[200]}`,
35+
'&:hover': {
36+
backgroundColor: appColors.neutral[50],
37+
},
38+
}));
39+
840
const ProfileDetail: React.FC<ProfileDetailTabProps> = ({ profile }) => {
9-
return <TreeDisplay data={profile?.data} collapsed={2} />;
41+
const handleExport = () => {
42+
const dataStr = JSON.stringify(profile?.data, null, 2);
43+
const dataBlob = new Blob([dataStr], { type: 'application/json' });
44+
const url = URL.createObjectURL(dataBlob);
45+
const link = document.createElement('a');
46+
link.href = url;
47+
link.download = `profile-${Date.now()}.json`;
48+
link.click();
49+
URL.revokeObjectURL(url);
50+
};
51+
52+
return (
53+
<Stack spacing={2} width="100%">
54+
<StyledPanel>
55+
<TreeDisplay data={profile?.data} collapsed={2} />
56+
</StyledPanel>
57+
<ExportButton startIcon={<FileDownload />} onClick={handleExport}>
58+
Export Profile JSON
59+
</ExportButton>
60+
</Stack>
61+
);
1062
};
1163

1264
export default ProfileDetail;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { describe, it, expect, vi } from 'vitest';
4+
import TagConfig from './TagConfig';
5+
import { ThemeProvider } from '@mui/material/styles';
6+
import { appTheme } from '@root/src/theme';
7+
import { TagConfigModel } from '@root/src/shared/models/tagConfigModel';
8+
9+
// Mock TreeDisplay component
10+
vi.mock('@root/src/pages/sidepanel/components/TreeDisplay', () => ({
11+
default: ({ data }: { data: any }) => <div data-testid="tree-display">{JSON.stringify(data)}</div>,
12+
}));
13+
14+
describe('TagConfig', () => {
15+
const mockTagConfig: TagConfigModel = {
16+
stream: 'test-stream',
17+
cid: ['test-cid-456'], // cid is string[] according to model
18+
version: '3.5.0',
19+
url: 'https://example.com',
20+
src: 'https://c.lytics.io/api/tag/test-account-123/latest.min.js',
21+
};
22+
23+
const renderWithTheme = (component: React.ReactElement) => {
24+
return render(<ThemeProvider theme={appTheme}>{component}</ThemeProvider>);
25+
};
26+
27+
it('should render the TagConfig component', () => {
28+
renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);
29+
30+
const treeDisplay = screen.getByTestId('tree-display');
31+
expect(treeDisplay).toBeInTheDocument();
32+
});
33+
34+
it('should pass tagConfig data to TreeDisplay', () => {
35+
renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);
36+
37+
const treeDisplay = screen.getByTestId('tree-display');
38+
expect(treeDisplay).toHaveTextContent(mockTagConfig.stream);
39+
expect(treeDisplay).toHaveTextContent(mockTagConfig.version);
40+
expect(treeDisplay).toHaveTextContent(mockTagConfig.url);
41+
});
42+
43+
it('should handle empty tagConfig', () => {
44+
const emptyConfig = {} as TagConfigModel;
45+
renderWithTheme(<TagConfig tagConfig={emptyConfig} />);
46+
47+
const treeDisplay = screen.getByTestId('tree-display');
48+
expect(treeDisplay).toBeInTheDocument();
49+
});
50+
51+
it('should handle different tagConfig versions', () => {
52+
const legacyConfig: TagConfigModel = {
53+
...mockTagConfig,
54+
version: '2.8.0',
55+
};
56+
57+
renderWithTheme(<TagConfig tagConfig={legacyConfig} />);
58+
59+
const treeDisplay = screen.getByTestId('tree-display');
60+
expect(treeDisplay).toHaveTextContent('2.8.0');
61+
});
62+
63+
it('should handle tagConfig with missing optional fields', () => {
64+
const partialConfig = {
65+
stream: 'test-stream',
66+
version: '3.5.0',
67+
} as TagConfigModel;
68+
69+
renderWithTheme(<TagConfig tagConfig={partialConfig} />);
70+
71+
const treeDisplay = screen.getByTestId('tree-display');
72+
expect(treeDisplay).toBeInTheDocument();
73+
expect(treeDisplay).toHaveTextContent('test-stream');
74+
});
75+
76+
it('should render TreeDisplay inside styled panel', () => {
77+
const { container } = renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);
78+
79+
const panel = container.querySelector('.MuiBox-root');
80+
const treeDisplay = screen.getByTestId('tree-display');
81+
82+
expect(panel).toContainElement(treeDisplay);
83+
});
84+
});
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
import React from 'react';
2+
import { Box } from '@mui/material';
3+
import { styled } from '@mui/material/styles';
4+
import { appColors } from '@root/src/theme/palette';
25
import { TagConfigModel } from '@root/src/shared/models/tagConfigModel';
36
import TreeDisplay from '@root/src/pages/sidepanel/components/TreeDisplay';
47

58
interface ConfigTabProps {
69
tagConfig: TagConfigModel;
710
}
811

12+
const StyledPanel = styled(Box)(({ theme }) => ({
13+
width: '100%',
14+
backgroundColor: appColors.common.darkPanel,
15+
borderRadius: theme.spacing(1), // 8px all corners
16+
padding: '0.625rem', // 10px inner padding
17+
cursor: 'default',
18+
transition: 'none',
19+
'&:hover': {
20+
boxShadow: 'none',
21+
transform: 'none',
22+
},
23+
}));
24+
925
const TagConfig: React.FC<ConfigTabProps> = ({ tagConfig }) => {
10-
return <TreeDisplay data={tagConfig} />;
26+
return (
27+
<StyledPanel>
28+
<TreeDisplay data={tagConfig} />
29+
</StyledPanel>
30+
);
1131
};
1232

1333
export default TagConfig;

src/pages/sidepanel/sections/TagStatus.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ interface TagStatusProps {
1616

1717
const StyledContainer = styled(Box)(({ theme }) => ({
1818
paddingInline: theme.spacing(1.25),
19+
paddingBottom: theme.spacing(12), // 96px bottom space to clear bottom navigation
20+
height: '100%',
21+
width: '100%',
22+
overflow: 'auto',
23+
boxSizing: 'border-box',
1924
cursor: 'default',
2025
transition: 'none',
2126
'&:hover': {

0 commit comments

Comments
 (0)