Skip to content

Commit 70bcd51

Browse files
committed
Implement global page structure
1 parent 3e9a234 commit 70bcd51

File tree

17 files changed

+543
-466
lines changed

17 files changed

+543
-466
lines changed

package-lock.json

+9-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/client/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@parcel/reporter-bundle-analyzer": "^2.12.0",
3636
"@parcel/reporter-bundle-buddy": "^2.12.0",
3737
"@types/babel__core": "^7",
38-
"@types/node": "^22.9.0",
38+
"@types/node": "^22.10.2",
3939
"@types/react": "^18.3.4",
4040
"@types/react-dom": "^18.3.0",
4141
"buffer": "^6.0.3",
@@ -74,7 +74,7 @@
7474
"@types/react": "^18.3.12",
7575
"@types/react-dom": "^18.3.1",
7676
"formik": "^2.4.6",
77-
"framer-motion": "^11.0.3",
77+
"framer-motion": "^10.16.5",
7878
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
7979
"maplibre-gl": "^3.6.2",
8080
"polished": "^4.3.1",

packages/client/src/App.tsx

+52-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import React from 'react';
22
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3-
import { ChakraProvider, Box, Container } from '@chakra-ui/react';
3+
import {
4+
ChakraProvider,
5+
Box,
6+
Container,
7+
Flex,
8+
Heading,
9+
Text,
10+
Badge,
11+
Divider
12+
} from '@chakra-ui/react';
413
import { StacApiProvider } from '@developmentseed/stac-react';
514
import { PluginConfigProvider } from '@stac-manager/data-core';
615

@@ -21,20 +30,25 @@ export const App = () => (
2130
<StacApiProvider apiUrl={process.env.REACT_APP_STAC_API!}>
2231
<PluginConfigProvider config={config}>
2332
<Router>
24-
<Container mx='auto' p='5' bgColor='white' maxW='container.lg'>
25-
<Box
33+
<Container
34+
maxW='container.lg'
35+
minH='100vh'
36+
display='flex'
37+
flexDirection='column'
38+
>
39+
<Flex
2640
as='header'
27-
borderBottom='1px dashed'
28-
borderColor='gray.300'
29-
mb='4'
30-
pb='4'
31-
display='flex'
41+
gap={4}
42+
alignItems='center'
43+
justifyContent='space-between'
44+
p={4}
3245
>
33-
<Box flex='1' fontWeight='bold' textTransform='uppercase'>
34-
STAC Admin
35-
</Box>
46+
<Heading as='p' size='sm'>
47+
STAC Manager
48+
</Heading>
49+
3650
<MainNavigation />
37-
</Box>
51+
</Flex>
3852
<Box as='main'>
3953
<Routes>
4054
<Route path='/' element={<Home />} />
@@ -60,6 +74,32 @@ export const App = () => (
6074
<Route path='*' element={<NotFound />} />
6175
</Routes>
6276
</Box>
77+
<Flex
78+
as='footer'
79+
gap={4}
80+
alignItems='center'
81+
justifyContent='space-between'
82+
mt='auto'
83+
p={4}
84+
>
85+
<Flex gap={4} alignItems='center'>
86+
<Text as='span'>
87+
Powered by{' '}
88+
<strong>
89+
STAC Manager{' '}
90+
<Badge bg='base.400a' color='surface.500' px='0.375rem'>
91+
{process.env.APP_VERSION}
92+
</Badge>
93+
</strong>{' '}
94+
</Text>
95+
<Divider
96+
orientation='vertical'
97+
borderColor='base.200a'
98+
h='1em'
99+
/>
100+
{new Date().getFullYear()}
101+
</Flex>
102+
</Flex>
63103
</Container>
64104
</Router>
65105
</PluginConfigProvider>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { Flex, Heading, Text, forwardRef, FlexProps } from '@chakra-ui/react';
3+
4+
interface InnerPageHeaderProps extends FlexProps {
5+
title: string;
6+
overline?: string;
7+
actions?: React.ReactNode;
8+
}
9+
10+
export const InnerPageHeader = forwardRef<InnerPageHeaderProps, 'div'>(
11+
({ title, overline, actions, ...rest }, ref) => {
12+
return (
13+
<Flex
14+
ref={ref}
15+
bg='base.50'
16+
borderRadius='md'
17+
p={4}
18+
direction='column'
19+
gap={2}
20+
{...rest}
21+
>
22+
{overline && (
23+
<Text as='p' color='base.400'>
24+
{overline}
25+
</Text>
26+
)}
27+
<Flex gap={4} justifyContent='space-between' alignItems='center'>
28+
<Heading size='md'>{title}</Heading>
29+
{actions && <Flex gap={2}>{actions}</Flex>}
30+
</Flex>
31+
</Flex>
32+
);
33+
}
34+
);
35+
36+
export const InnerPageHeaderSticky = forwardRef<InnerPageHeaderProps, 'div'>(
37+
(props, ref) => {
38+
const [isAtTop, setIsAtTop] = useState(false);
39+
40+
const localRef = useRef<HTMLDivElement | null>(null);
41+
42+
useEffect(() => {
43+
const el = localRef.current;
44+
if (!el) return;
45+
const observer = new IntersectionObserver(
46+
([entry]) => {
47+
setIsAtTop(entry.intersectionRatio < 1);
48+
},
49+
{ threshold: [1] }
50+
);
51+
52+
observer.observe(el);
53+
54+
return () => {
55+
observer.unobserve(el);
56+
};
57+
}, []);
58+
59+
const headerRef: React.RefCallback<HTMLDivElement> = (v) => {
60+
localRef.current = v;
61+
if (typeof ref === 'function') {
62+
ref(v);
63+
} else if (ref != null) {
64+
(ref as React.MutableRefObject<any>).current = v;
65+
}
66+
};
67+
68+
return (
69+
<InnerPageHeader
70+
ref={headerRef}
71+
position='sticky'
72+
top='-1px'
73+
boxShadow={isAtTop ? 'md' : 'none'}
74+
zIndex={100}
75+
{...props}
76+
/>
77+
);
78+
}
79+
);

packages/client/src/components/MainNavigation.tsx

+29-21
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
1-
import { Link as RouterLink } from "react-router-dom";
2-
import { Box, List, ListItem, Link } from "@chakra-ui/react";
1+
import React from 'react';
2+
import { Box, List, ListItem, Button, ButtonProps } from '@chakra-ui/react';
3+
import {
4+
CollecticonFolder,
5+
CollecticonPlusSmall
6+
} from '@devseed-ui/collecticons-chakra';
37

4-
type NavItemProps = React.PropsWithChildren<{
5-
to: string
6-
}>
8+
import SmartLink, { SmartLinkProps } from './SmartLink';
79

8-
function NavItem({ to, children }: NavItemProps) {
10+
function NavItem(props: ButtonProps & SmartLinkProps) {
911
return (
1012
<ListItem>
11-
<Link
12-
as={RouterLink}
13-
to={to}
14-
borderRadius="5"
15-
px="2"
16-
py="1"
17-
_hover={{ bgColor: "gray.100" }}
18-
>
19-
{children}
20-
</Link>
13+
<Button
14+
as={SmartLink}
15+
variant='ghost'
16+
sx={{
17+
'&:hover': {
18+
textDecoration: 'none'
19+
}
20+
}}
21+
{...props}
22+
/>
2123
</ListItem>
2224
);
2325
}
2426

2527
function MainNavigation() {
2628
return (
27-
<Box as="nav" aria-label="Main">
28-
<List my="0" display="flex">
29-
<NavItem to="/">Home</NavItem>
30-
<NavItem to="/collections/">Collections</NavItem>
31-
<NavItem to="/items/">Items</NavItem>
29+
<Box as='nav' aria-label='Main'>
30+
<List display='flex' gap={2}>
31+
<NavItem to='/collections/' leftIcon={<CollecticonFolder />}>
32+
Collections
33+
</NavItem>
34+
<NavItem to='/items/' leftIcon={<CollecticonFolder />}>
35+
Items
36+
</NavItem>
37+
<NavItem to='/collections/new' leftIcon={<CollecticonPlusSmall />}>
38+
Create
39+
</NavItem>
3240
</List>
3341
</Box>
3442
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { Link as ChLink, LinkProps } from '@chakra-ui/react';
3+
import { Link } from 'react-router-dom';
4+
5+
export interface SmartLinkProps extends LinkProps {
6+
to: string;
7+
}
8+
9+
export default React.forwardRef<HTMLLinkElement, SmartLinkProps>(
10+
function SmartLink(props, ref) {
11+
const { to, ...rest } = props;
12+
13+
const isExternal =
14+
to.match(/^(https?:)?\/\//) || to.match(/^(mailto|tel):/);
15+
16+
return isExternal ? (
17+
<ChLink ref={ref} href={to} {...rest} />
18+
) : (
19+
<ChLink ref={ref} as={Link} to={to} {...rest} />
20+
);
21+
}
22+
);
+4-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import HeadingLead from "./HeadingLead";
2-
import Loading from "./Loading";
3-
import MainNavigation from "./MainNavigation";
1+
import HeadingLead from './HeadingLead';
2+
import Loading from './Loading';
3+
import MainNavigation from './MainNavigation';
44

5-
export {
6-
HeadingLead,
7-
Loading,
8-
MainNavigation
9-
};
5+
export { HeadingLead, Loading, MainNavigation };

0 commit comments

Comments
 (0)