mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
5.3 KiB
206 lines
5.3 KiB
import React, { useEffect, useRef, useState } from 'react';
|
|
import {
|
|
Box,
|
|
Flex,
|
|
Portal,
|
|
space,
|
|
Fade,
|
|
themeColor,
|
|
color,
|
|
BoxProps,
|
|
Grid,
|
|
Stack,
|
|
} from '@stacks/ui';
|
|
import { useDocSearchKeyboardEvents } from '@docsearch/react';
|
|
import { Text } from '@components/typography';
|
|
import { SearchIcon } from '@components/icons/search';
|
|
import Router from 'next/router';
|
|
import Link from 'next/link';
|
|
import { getCapsizeStyles } from '@components/mdx/typography';
|
|
import { useAppState } from '@common/hooks/use-app-state';
|
|
import { css, Theme } from '@stacks/ui-core';
|
|
import { SEARCH_DOCS } from '@common/constants_that_require_translations';
|
|
|
|
const getLocalUrl = href => {
|
|
const _url = new URL(href);
|
|
const url = href
|
|
.replace(_url.origin, '')
|
|
.replace('#__next', '')
|
|
.replace('.html', '')
|
|
.replace('storage/clidocs', 'core/cmdLineRef');
|
|
return url;
|
|
};
|
|
|
|
function Hit({ hit, children }: any) {
|
|
const url = getLocalUrl(hit.url);
|
|
return (
|
|
<Link href={url} as={url} passHref scroll={false}>
|
|
<a>{children}</a>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
const navigator = {
|
|
navigate: async ({ suggestionUrl }: any) => {
|
|
const url = getLocalUrl(suggestionUrl);
|
|
return Router.push(url, url);
|
|
},
|
|
};
|
|
|
|
const Key: React.FC<BoxProps> = React.memo(({ children, ...rest }) => (
|
|
<Grid
|
|
style={{
|
|
placeItems: 'center',
|
|
}}
|
|
size="18px"
|
|
bg={'rgba(0,0,0,0.11)'}
|
|
borderRadius="3px"
|
|
opacity={0.65}
|
|
{...rest}
|
|
>
|
|
<Text
|
|
{...{
|
|
color: color('text-body'),
|
|
display: 'block',
|
|
transform: 'translateY(1px)',
|
|
...getCapsizeStyles(12, 12),
|
|
}}
|
|
>
|
|
{children}
|
|
</Text>
|
|
</Grid>
|
|
));
|
|
|
|
const searchOptions = {
|
|
apiKey: '9040ba6d60f5ecb36eafc26396288875',
|
|
indexName: 'blockstack',
|
|
navigator,
|
|
};
|
|
|
|
let DocSearchModal: any = null;
|
|
|
|
export const SearchBox: React.FC<BoxProps> = React.memo(props => {
|
|
const { setState, searchModal } = useAppState();
|
|
|
|
const importDocSearchModalIfNeeded = React.useCallback(function importDocSearchModalIfNeeded() {
|
|
if (DocSearchModal) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.all([import('@docsearch/react/modal')]).then(([{ DocSearchModal: Modal }]) => {
|
|
DocSearchModal = Modal;
|
|
});
|
|
}, []);
|
|
|
|
const onOpen = React.useCallback(
|
|
function onOpen() {
|
|
void importDocSearchModalIfNeeded().then(() => {
|
|
console.log('reopening');
|
|
setState(state => ({ ...state, searchModal: 'open' }));
|
|
});
|
|
},
|
|
[importDocSearchModalIfNeeded]
|
|
);
|
|
|
|
const onClose = React.useCallback(
|
|
function onClose() {
|
|
setState(state => ({ ...state, searchModal: 'closed' }));
|
|
},
|
|
[setState]
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (searchModal === 'open') onOpen();
|
|
}, [searchModal]);
|
|
|
|
const searchButtonRef = React.useRef(null);
|
|
const isOpen = Boolean(searchModal === 'open' && DocSearchModal);
|
|
|
|
useDocSearchKeyboardEvents({ isOpen, onOpen, onClose, searchButtonRef });
|
|
|
|
return (
|
|
<>
|
|
<Portal>
|
|
<Fade in={isOpen}>
|
|
{styles => (
|
|
<Box
|
|
position="absolute"
|
|
zIndex={9999}
|
|
style={{ ...styles }}
|
|
css={(theme: Theme) =>
|
|
css({
|
|
'.DocSearch.DocSearch-Container': {
|
|
position: 'fixed',
|
|
},
|
|
})(theme)
|
|
}
|
|
>
|
|
<DocSearchModal
|
|
initialScrollY={window.scrollY}
|
|
onClose={onClose}
|
|
hitComponent={Hit}
|
|
{...searchOptions}
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Fade>
|
|
</Portal>
|
|
<Box
|
|
bg={color('bg-alt')}
|
|
width="100%"
|
|
borderRadius="12px"
|
|
display={['none', 'none', 'block', 'block']}
|
|
border="1px solid"
|
|
borderColor={isOpen ? 'rgba(170, 179, 255, 0.8)' : color('border')}
|
|
boxShadow={
|
|
isOpen ? '0 0 0 3px rgba(170, 179, 255, 0.25)' : '0 0 0 3px rgba(170, 179, 255, 0)'
|
|
}
|
|
transition="border-color 0.2s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.2s cubic-bezier(0.23, 1, 0.32, 1)"
|
|
_hover={{
|
|
borderColor: 'rgba(170, 179, 255, 0.8)',
|
|
boxShadow: '0 0 0 3px rgba(170, 179, 255, 0.25)',
|
|
cursor: 'pointer',
|
|
}}
|
|
style={{
|
|
userSelect: 'none',
|
|
}}
|
|
{...props}
|
|
>
|
|
<Flex alignItems="center" justifyContent="space-between">
|
|
<Flex
|
|
ref={searchButtonRef}
|
|
onClick={onOpen}
|
|
px={space('base-tight')}
|
|
py={space('tight')}
|
|
alignItems="center"
|
|
_hover={{ borderColor: themeColor('blue.400') }}
|
|
>
|
|
<Box
|
|
transform="scaleX(-1)"
|
|
mr={space('tight')}
|
|
opacity={0.6}
|
|
color={color('text-caption')}
|
|
>
|
|
<SearchIcon size="18px" />
|
|
</Box>
|
|
<Text
|
|
opacity={0.8}
|
|
{...{
|
|
color: color('text-caption'),
|
|
...getCapsizeStyles(14, 28),
|
|
}}
|
|
>
|
|
{SEARCH_DOCS}
|
|
</Text>
|
|
</Flex>
|
|
<Stack isInline spacing="3px" pr="base">
|
|
<Key>⌘</Key>
|
|
<Key>K</Key>
|
|
</Stack>
|
|
</Flex>
|
|
</Box>
|
|
</>
|
|
);
|
|
});
|
|
|
|
export default SearchBox;
|
|
|