feat: add dropdown component

This commit is contained in:
Sonny
2024-05-12 20:06:05 +02:00
committed by Sonny
parent 3531038321
commit 0f1dc9b69c
13 changed files with 142 additions and 16 deletions

View File

@@ -26,13 +26,11 @@ export default class FaviconsController {
];
async index(ctx: HttpContext) {
console.log('0');
const url = ctx.request.qs()?.url;
if (!url) {
throw new Error('Missing URL');
}
console.log('1');
const faviconRequestUrl = this.buildFaviconUrl(url, '/favicon.ico');
try {
const favicon = await this.getFavicon(faviconRequestUrl);
@@ -42,7 +40,6 @@ export default class FaviconsController {
`[Favicon] [first: ${faviconRequestUrl}] Unable to retrieve favicon from favicon.ico url`
);
}
console.log('2');
const requestDocument = await this.makeRequestWithUserAgent(url);
const documentAsText = await requestDocument.text();
@@ -57,7 +54,6 @@ export default class FaviconsController {
return this.sendDefaultImage(ctx);
}
console.log('3');
const finalUrl = this.buildFaviconUrl(requestDocument.url, faviconPath);
try {
if (!faviconPath) {
@@ -186,14 +182,12 @@ export default class FaviconsController {
Buffer.from(base64, 'base64');
private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) {
console.log('ouiiiiiiii', type, size);
ctx.response.header('Content-Type', type);
ctx.response.header('Content-Length', size);
ctx.response.send(buffer);
}
private sendDefaultImage(ctx: HttpContext) {
console.log('oui');
const readStream = createReadStream(
resolve(process.cwd(), './public/empty-image.png')
);

View File

@@ -0,0 +1,56 @@
import KEYS from '#constants/keys';
import styled from '@emotion/styled';
import { ReactNode, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import DropdownContainer from '~/components/common/dropdown/dropdown_container';
import DropdownLabel from '~/components/common/dropdown/dropdown_label';
import useClickOutside from '~/hooks/use_click_outside';
import useGlobalHotkeys from '~/hooks/use_global_hotkeys';
import useToggle from '~/hooks/use_modal';
const DropdownStyle = styled.div<{ opened: boolean }>(({ opened, theme }) => ({
cursor: 'pointer',
userSelect: 'none',
position: 'relative',
minWidth: 'fit-content',
width: 'fit-content',
maxWidth: '250px',
backgroundColor: opened ? theme.colors.secondary : theme.colors.background,
padding: '4px',
borderRadius: theme.border.radius,
'&:hover': {
backgroundColor: theme.colors.secondary,
},
'& svg': {
height: '24px',
width: '24px',
},
}));
export default function Dropdown({
children,
label,
}: {
children: ReactNode;
label: ReactNode | string;
}) {
const dropdownRef = useRef<HTMLDivElement>(null);
const { isShowing, toggle, close } = useToggle();
const { globalHotkeysEnabled } = useGlobalHotkeys();
useClickOutside(dropdownRef, close);
useHotkeys(KEYS.ESCAPE_KEY, close, {
enabled: globalHotkeysEnabled,
enableOnFormTags: ['INPUT'],
});
return (
<DropdownStyle opened={isShowing} onClick={toggle} ref={dropdownRef}>
<DropdownLabel>{label}</DropdownLabel>
<DropdownContainer show={isShowing}>{children}</DropdownContainer>
</DropdownStyle>
);
}

View File

@@ -0,0 +1,17 @@
import styled from '@emotion/styled';
const DropdownContainer = styled.div<{ show: boolean }>(({ show, theme }) => ({
position: 'absolute',
top: 'calc(100% + 0.5em)',
right: 0,
minWidth: '175px',
backgroundColor: show ? theme.colors.secondary : theme.colors.background,
border: `2px solid ${theme.colors.secondary}`,
borderRadius: theme.border.radius,
boxShadow: theme.colors.boxShadow,
display: show ? 'flex' : 'none',
flexDirection: 'column',
overflow: 'hidden',
}));
export default DropdownContainer;

View File

@@ -0,0 +1,16 @@
import styled from '@emotion/styled';
const DropdownItem = styled.div(({ theme }) => ({
fontSize: '14px',
padding: '8px 12px',
display: 'flex',
gap: '0.35em',
alignItems: 'center',
borderRadius: theme.border.radius,
'&:hover': {
backgroundColor: theme.colors.background,
},
}));
export default DropdownItem;

View File

@@ -0,0 +1,11 @@
import styled from '@emotion/styled';
const DropdownLabel = styled.p(({ theme }) => ({
height: 'auto',
width: 'auto',
color: theme.colors.font,
display: 'flex',
gap: '0.35em',
}));
export default DropdownLabel;

View File

@@ -4,13 +4,16 @@ import { router } from '@inertiajs/react';
import { ReactNode } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import useGlobalHotkeys from '~/hooks/use_global_hotkeys';
import useSearchParam from '~/hooks/use_search_param';
import { appendCollectionId } from '~/lib/navigation';
export default function BackToDashboard({ children }: { children: ReactNode }) {
const collectionId = useSearchParam('collectionId');
const { globalHotkeysEnabled } = useGlobalHotkeys();
useHotkeys(
KEYS.ESCAPE_KEY,
() => {
router.visit(PATHS.DASHBOARD);
router.visit(appendCollectionId(PATHS.DASHBOARD, collectionId));
},
{ enabled: globalHotkeysEnabled, enableOnFormTags: ['INPUT'] }
);

View File

@@ -0,0 +1,22 @@
import { RefObject, useEffect } from 'react';
// Source : https://stackoverflow.com/a/63359693
/**
* This Hook can be used for detecting clicks outside the Opened Menu
*/
export default function useClickOutside(
ref: RefObject<HTMLElement>,
onClickOutside: () => void
) {
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (ref?.current && !ref.current?.contains(event.target as any)) {
onClickOutside();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [ref, onClickOutside]);
}

View File

@@ -1,6 +1,6 @@
import { useState } from 'react';
const useModal = (defaultValue: boolean = false) => {
const useToggle = (defaultValue: boolean = false) => {
const [isShowing, setIsShowing] = useState<boolean>(defaultValue);
const toggle = () => setIsShowing((value) => !value);
@@ -15,4 +15,4 @@ const useModal = (defaultValue: boolean = false) => {
};
};
export default useModal;
export default useToggle;

View File

@@ -2,7 +2,7 @@ import { useForm } from '@inertiajs/react';
import { ChangeEvent, FormEvent, useMemo } from 'react';
import FormField from '~/components/common/form/_form_field';
import TextBox from '~/components/common/form/textbox';
import BackToDashboard from '~/components/common/navigation/bask_to_dashboard';
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
import FormLayout from '~/components/layouts/form_layout';
import { Visibility } from '../../../app/enums/visibility';

View File

@@ -16,7 +16,7 @@ import CollectionsContext from '~/contexts/collections_context';
import FavoritesContext from '~/contexts/favorites_context';
import GlobalHotkeysContext from '~/contexts/global_hotkeys_context';
import { useMediaQuery } from '~/hooks/use_media_query';
import useModal from '~/hooks/use_modal';
import useToggle from '~/hooks/use_modal';
interface HomePageProps {
collections: Collection[];
@@ -31,7 +31,7 @@ const SideBar = styled.div(({ theme }) => ({
export default function HomePage(props: Readonly<HomePageProps>) {
const isMobile = useMediaQuery('(max-width: 768px)');
const { isShowing, open, close } = useModal();
const { isShowing, open, close } = useToggle();
const handlers = useSwipeable({
trackMouse: true,
onSwipedRight: open,

View File

@@ -3,7 +3,7 @@ import { useForm } from '@inertiajs/react';
import { ChangeEvent, FormEvent, useMemo } from 'react';
import FormField from '~/components/common/form/_form_field';
import TextBox from '~/components/common/form/textbox';
import BackToDashboard from '~/components/common/navigation/bask_to_dashboard';
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
import FormLayout from '~/components/layouts/form_layout';
import useSearchParam from '~/hooks/use_search_param';
import { isValidHttpUrl } from '~/lib/navigation';
@@ -46,6 +46,7 @@ export default function CreateLinkPage({
title="Create a link"
handleSubmit={handleSubmit}
canSubmit={canSubmit}
collectionId={collectionId}
>
<BackToDashboard>
<TextBox

View File

@@ -34,7 +34,7 @@ export const lightTheme: Theme = {
primary: primaryColor,
secondary: '#fff',
white: '#fff',
white: '#ffffff',
lightGrey: '#dadce0',
grey: '#888888',
@@ -46,6 +46,8 @@ export const lightTheme: Theme = {
lightRed,
yellow,
boxShadow: '0 0 1em 0 rgba(102, 102, 102, 0.25)',
},
border,
@@ -60,10 +62,10 @@ export const darkTheme: Theme = {
primary: '#4fadfc',
secondary: '#323a47',
white: '#fff',
white: '#ffffff',
lightGrey: '#323a47',
grey: '#888888',
grey: '#999999',
lightBlue,
blue: '#4fadfc',
@@ -72,6 +74,8 @@ export const darkTheme: Theme = {
lightRed,
yellow,
boxShadow: '0 0 1em 0 rgb(40 40 40)',
},
border,

View File

@@ -20,6 +20,8 @@ declare module '@emotion/react' {
lightRed: string;
yellow: string;
boxShadow: string;
};
border: {