mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat: create settings modal
This commit is contained in:
12
inertia/components/common/modal/_modal_body.tsx
Normal file
12
inertia/components/common/modal/_modal_body.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const ModalBody = styled.div({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
overflow: 'auto',
|
||||
});
|
||||
|
||||
export default ModalBody;
|
||||
23
inertia/components/common/modal/_modal_container.tsx
Normal file
23
inertia/components/common/modal/_modal_container.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const ModalContainer = styled.div(({ theme }) => ({
|
||||
minWidth: '500px',
|
||||
background: theme.colors.secondary,
|
||||
padding: '1em',
|
||||
borderRadius: theme.border.radius,
|
||||
marginTop: '6em',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
boxShadow: theme.colors.boxShadow,
|
||||
|
||||
[`@media (max-width: ${theme.media.mobile})`]: {
|
||||
maxHeight: 'calc(100% - 2em)',
|
||||
width: 'calc(100% - 2em)',
|
||||
minWidth: 'unset',
|
||||
marginTop: '1em',
|
||||
},
|
||||
}));
|
||||
|
||||
export default ModalContainer;
|
||||
20
inertia/components/common/modal/_modal_header.tsx
Normal file
20
inertia/components/common/modal/_modal_header.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const ModalHeader = styled.h3({
|
||||
width: '100%',
|
||||
marginBottom: '0.75em',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
});
|
||||
|
||||
const ModalCloseBtn = styled.button(({ theme }) => ({
|
||||
cursor: 'pointer',
|
||||
color: theme.colors.primary,
|
||||
backgroundColor: 'transparent',
|
||||
border: 0,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
export { ModalHeader, ModalCloseBtn };
|
||||
18
inertia/components/common/modal/_modal_wrapper.tsx
Normal file
18
inertia/components/common/modal/_modal_wrapper.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { rgba } from '~/lib/color';
|
||||
|
||||
const ModalWrapper = styled.div(({ theme }) => ({
|
||||
zIndex: 9999,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: rgba(theme.colors.black, 0.35),
|
||||
backdropFilter: 'blur(0.25em)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}));
|
||||
|
||||
export default ModalWrapper;
|
||||
59
inertia/components/common/modal/modal.tsx
Normal file
59
inertia/components/common/modal/modal.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Fragment, ReactNode, useEffect, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { IoClose } from 'react-icons/io5';
|
||||
import ModalBody from '~/components/common/modal/_modal_body';
|
||||
import ModalContainer from '~/components/common/modal/_modal_container';
|
||||
import {
|
||||
ModalCloseBtn,
|
||||
ModalHeader,
|
||||
} from '~/components/common/modal/_modal_header';
|
||||
import ModalWrapper from '~/components/common/modal/_modal_wrapper';
|
||||
import TextEllipsis from '~/components/common/text_ellipsis';
|
||||
import useClickOutside from '~/hooks/use_click_outside';
|
||||
import useGlobalHotkeys from '~/hooks/use_global_hotkeys';
|
||||
import useShortcut from '~/hooks/use_shortcut';
|
||||
|
||||
interface ModalProps {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
opened: boolean;
|
||||
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
title,
|
||||
children,
|
||||
opened = true,
|
||||
close,
|
||||
}: ModalProps) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const { setGlobalHotkeysEnabled } = useGlobalHotkeys();
|
||||
|
||||
useClickOutside(modalRef, close);
|
||||
useShortcut('ESCAPE_KEY', close, { ignoreGlobalHotkeysStatus: true });
|
||||
|
||||
useEffect(() => setGlobalHotkeysEnabled(!opened), [opened]);
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
return (
|
||||
opened &&
|
||||
createPortal(
|
||||
<ModalWrapper>
|
||||
<ModalContainer ref={modalRef}>
|
||||
<ModalHeader>
|
||||
{title && <TextEllipsis>{title}</TextEllipsis>}
|
||||
<ModalCloseBtn onClick={close}>
|
||||
<IoClose size={20} />
|
||||
</ModalCloseBtn>
|
||||
</ModalHeader>
|
||||
<ModalBody>{children}</ModalBody>
|
||||
</ModalContainer>
|
||||
</ModalWrapper>,
|
||||
document.body
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { rgba } from '~/lib/color';
|
||||
|
||||
const RoundedImage = styled.img({
|
||||
'borderRadius': '50%',
|
||||
const RoundedImage = styled.img(({ theme }) => {
|
||||
const transparentBlack = rgba(theme.colors.black, 0.1);
|
||||
return {
|
||||
borderRadius: '50%',
|
||||
|
||||
'&:hover': {
|
||||
boxShadow: '0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1)',
|
||||
boxShadow: `0 1px 3px 0 ${transparentBlack}, 0 1px 2px -1px ${transparentBlack}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default RoundedImage;
|
||||
|
||||
@@ -21,7 +21,7 @@ const LinksWrapper = styled.div({
|
||||
const CollectionHeaderWrapper = styled.h2(({ theme }) => ({
|
||||
fontWeight: 400,
|
||||
color: theme.colors.font,
|
||||
paddingRight: '1.1em',
|
||||
paddingInline: '0.8em 1.1em',
|
||||
display: 'flex',
|
||||
gap: '0.4em',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { Link } from '@inertiajs/react';
|
||||
|
||||
export const Item = styled.div(({ theme }) => ({
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
height: '40px',
|
||||
width: '250px',
|
||||
|
||||
25
inertia/components/dashboard/side_nav/open_settings.tsx
Normal file
25
inertia/components/dashboard/side_nav/open_settings.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { BsGear } from 'react-icons/bs';
|
||||
import Modal from '~/components/common/modal/modal';
|
||||
import { Item } from '~/components/dashboard/side_nav/nav_item';
|
||||
import useToggle from '~/hooks/use_modal';
|
||||
|
||||
const SettingsButton = styled(Item)(({ theme }) => ({
|
||||
color: theme.colors.grey,
|
||||
}));
|
||||
|
||||
export default function OpenSettingsButton() {
|
||||
const { isShowing, open, close } = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsButton onClick={open}>
|
||||
<BsGear />
|
||||
Settings
|
||||
</SettingsButton>
|
||||
<Modal title="Settings" opened={isShowing} close={close}>
|
||||
Modal settings
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { AiOutlineFolderAdd } from 'react-icons/ai';
|
||||
import { BsGear } from 'react-icons/bs';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { MdOutlineAdminPanelSettings } from 'react-icons/md';
|
||||
import FavoriteList from '~/components/dashboard/side_nav/favorite/favorite_list';
|
||||
import { Item, ItemLink } from '~/components/dashboard/side_nav/nav_item';
|
||||
import OpenSettingsButton from '~/components/dashboard/side_nav/open_settings';
|
||||
import UserCard from '~/components/dashboard/side_nav/user_card';
|
||||
import useActiveCollection from '~/hooks/use_active_collection';
|
||||
import { appendCollectionId } from '~/lib/navigation';
|
||||
@@ -21,10 +21,6 @@ const AdminButton = styled(Item)(({ theme }) => ({
|
||||
color: theme.colors.lightRed,
|
||||
}));
|
||||
|
||||
const SettingsButton = styled(Item)(({ theme }) => ({
|
||||
color: theme.colors.grey,
|
||||
}));
|
||||
|
||||
const AddButton = styled(ItemLink)(({ theme }) => ({
|
||||
color: theme.colors.primary,
|
||||
}));
|
||||
@@ -38,10 +34,7 @@ export default function SideNavigation() {
|
||||
<AdminButton>
|
||||
<MdOutlineAdminPanelSettings /> Administrator
|
||||
</AdminButton>
|
||||
<SettingsButton>
|
||||
<BsGear />
|
||||
Settings
|
||||
</SettingsButton>
|
||||
<OpenSettingsButton />
|
||||
<AddButton
|
||||
href={appendCollectionId(
|
||||
route('link.create-form').url,
|
||||
|
||||
@@ -2,10 +2,18 @@ import KEYS from '#constants/keys';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import useGlobalHotkeys from '~/hooks/use_global_hotkeys';
|
||||
|
||||
export default function useShortcut(key: keyof typeof KEYS, cb: () => void) {
|
||||
type ShortcutOptions = { ignoreGlobalHotkeysStatus?: boolean };
|
||||
|
||||
export default function useShortcut(
|
||||
key: keyof typeof KEYS,
|
||||
cb: () => void,
|
||||
options: ShortcutOptions = {
|
||||
ignoreGlobalHotkeysStatus: false,
|
||||
}
|
||||
) {
|
||||
const { globalHotkeysEnabled } = useGlobalHotkeys();
|
||||
return useHotkeys(KEYS[key], cb, {
|
||||
enabled: globalHotkeysEnabled,
|
||||
enabled: !options.ignoreGlobalHotkeysStatus ? globalHotkeysEnabled : true,
|
||||
enableOnFormTags: ['INPUT'],
|
||||
});
|
||||
}
|
||||
|
||||
6
inertia/lib/color.ts
Normal file
6
inertia/lib/color.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import hexRgb from 'hex-rgb';
|
||||
|
||||
export const rgba = (hex: string, alpha: number) => {
|
||||
const rgb = hexRgb(hex, { format: 'array' }).slice(0, -1).join(',');
|
||||
return `rgba(${rgb},${alpha})`;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Theme } from '@emotion/react';
|
||||
import { rgba } from '~/lib/color';
|
||||
|
||||
export const primaryColor = '#3f88c5';
|
||||
export const primaryDarkColor = '#005aa5';
|
||||
@@ -32,6 +33,7 @@ export const lightTheme: Theme = {
|
||||
primary: primaryColor,
|
||||
secondary: '#fff',
|
||||
|
||||
black: '#333',
|
||||
white: '#ffffff',
|
||||
|
||||
lightGrey: '#dadce0',
|
||||
@@ -45,7 +47,7 @@ export const lightTheme: Theme = {
|
||||
|
||||
yellow: '#FF8A08',
|
||||
|
||||
boxShadow: '0 0 1em 0 rgba(102, 102, 102, 0.25)',
|
||||
boxShadow: `0 0 1em 0 ${rgba('#aaa', 0.4)}`,
|
||||
},
|
||||
|
||||
border,
|
||||
@@ -60,6 +62,7 @@ export const darkTheme: Theme = {
|
||||
primary: '#4fadfc',
|
||||
secondary: '#323a47',
|
||||
|
||||
black: '#333',
|
||||
white: '#ffffff',
|
||||
|
||||
lightGrey: '#323a47',
|
||||
@@ -73,7 +76,7 @@ export const darkTheme: Theme = {
|
||||
|
||||
yellow: '#ffc107',
|
||||
|
||||
boxShadow: '0 0 1em 0 rgb(40 40 40)',
|
||||
boxShadow: `0 0 1em 0 ${rgba('#111', 0.4)}`,
|
||||
},
|
||||
|
||||
border,
|
||||
|
||||
1
inertia/types/emotion.d.ts
vendored
1
inertia/types/emotion.d.ts
vendored
@@ -8,6 +8,7 @@ declare module '@emotion/react' {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
|
||||
black: string;
|
||||
white: string;
|
||||
|
||||
lightGrey: string;
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"@izzyjs/route": "^1.1.0-0",
|
||||
"@vinejs/vine": "^2.0.0",
|
||||
"edge.js": "^6.0.2",
|
||||
"hex-rgb": "^5.0.0",
|
||||
"i18next": "^23.11.3",
|
||||
"luxon": "^3.4.4",
|
||||
"node-html-parser": "^6.1.13",
|
||||
@@ -6399,6 +6400,17 @@
|
||||
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/hex-rgb": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-5.0.0.tgz",
|
||||
"integrity": "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"@izzyjs/route": "^1.1.0-0",
|
||||
"@vinejs/vine": "^2.0.0",
|
||||
"edge.js": "^6.0.2",
|
||||
"hex-rgb": "^5.0.0",
|
||||
"i18next": "^23.11.3",
|
||||
"luxon": "^3.4.4",
|
||||
"node-html-parser": "^6.1.13",
|
||||
|
||||
Reference in New Issue
Block a user