mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat: add dropdown component
This commit is contained in:
@@ -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')
|
||||
);
|
||||
|
||||
56
inertia/components/common/dropdown/dropdown.tsx
Normal file
56
inertia/components/common/dropdown/dropdown.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
17
inertia/components/common/dropdown/dropdown_container.tsx
Normal file
17
inertia/components/common/dropdown/dropdown_container.tsx
Normal 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;
|
||||
16
inertia/components/common/dropdown/dropdown_item.tsx
Normal file
16
inertia/components/common/dropdown/dropdown_item.tsx
Normal 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;
|
||||
11
inertia/components/common/dropdown/dropdown_label.tsx
Normal file
11
inertia/components/common/dropdown/dropdown_label.tsx
Normal 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;
|
||||
@@ -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'] }
|
||||
);
|
||||
22
inertia/hooks/use_click_outside.tsx
Normal file
22
inertia/hooks/use_click_outside.tsx
Normal 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]);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
2
inertia/types/emotion.d.ts
vendored
2
inertia/types/emotion.d.ts
vendored
@@ -20,6 +20,8 @@ declare module '@emotion/react' {
|
||||
lightRed: string;
|
||||
|
||||
yellow: string;
|
||||
|
||||
boxShadow: string;
|
||||
};
|
||||
|
||||
border: {
|
||||
|
||||
Reference in New Issue
Block a user