Update useModal hook
All checks were successful
forgejo/Procyon/procyon/pipeline/head This commit looks good

This commit is contained in:
Roman Jaroš 2023-11-19 22:26:02 +01:00 committed by Roman Jaroš
parent 4e84d18090
commit bc7aeb3f01
16 changed files with 88 additions and 115 deletions

View file

@ -11,14 +11,14 @@
"vscode": {
"extensions": [
"EditorConfig.EditorConfig",
"rvest.vs-code-prettier-eslint",
"bradlc.vscode-tailwindcss",
"unifiedjs.vscode-mdx",
"naumovs.color-highlight",
"dbaeumer.vscode-eslint",
"hediet.tasks-statusbar",
"BeardedBear.beardedicons",
"mia-hall.vscode-git-branch-sidebar"
"mia-hall.vscode-git-branch-sidebar",
"esbenp.prettier-vscode"
]
}
}

View file

@ -3,7 +3,6 @@ import { omit } from 'ramda';
import { format as formatFn } from 'date-fns';
import { TodayIcon } from '@procyon/components/Icons/Today';
import { ModalId } from '@procyon/components/Modal/context';
import { useModal } from '@procyon/components/Modal/hooks';
import { InputElementType } from '@procyon/types/form';
@ -22,7 +21,10 @@ export type DatePickerProps<V = Date> = Omit<
export const DatePicker: React.FC<DatePickerProps> = (props) => {
const { onChange, label, name, format, ...others } = props;
const { closeModal, openModal, registerModal } = useModal();
const [openModal, { closeModal }] = useModal(name, {
title: label,
style: { width: 400 },
});
const [value, setValue] = useState<Date>();
@ -32,7 +34,7 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
}
}, [props.value]);
const handleCloseModal = (id: ModalId) => closeModal(id);
const handleCloseModal = () => closeModal(name);
const handleDateClick = (value: Date): void => {
setValue(value);
@ -50,17 +52,13 @@ export const DatePicker: React.FC<DatePickerProps> = (props) => {
{...props}
/>
),
[format, name, value]
[format, name, value],
);
const handleOpenModal = (): void => {
registerModal({
id: name,
title: label,
style: { width: 400 },
Component: modalComponent
openModal({
Component: modalComponent,
});
openModal(name);
};
return (

View file

@ -4,7 +4,7 @@ import { Calendar } from '../../Calendar';
import { ModalId } from '../../Modal/context';
export type DatePickerModalProps = {
closeModal: (id: ModalId) => void;
closeModal: () => void;
format: ModalId;
name: ModalId;
onChange: (value: Date) => void;
@ -12,14 +12,14 @@ export type DatePickerModalProps = {
};
export const DatePickerModal: FC<DatePickerModalProps> = (props) => {
const { onChange, closeModal, name, value } = props;
const { onChange, closeModal, value } = props;
const setActiveOption = useCallback(
(value: Date) => {
onChange?.(value);
closeModal(name);
closeModal();
},
[onChange, closeModal, name]
[onChange, closeModal],
);
return <Calendar selected={value ?? undefined} value={value} onClick={setActiveOption} enableHover />;

View file

@ -1,4 +1,4 @@
import React, { FC, useEffect } from 'react';
import React, { FC } from 'react';
import { StatusEnum } from '@procyon/types/enums';
@ -7,29 +7,15 @@ import { Modal } from '../context';
import { useModal } from '../hooks';
export type AddModalButtonProps = {
Component: FC;
modalTitle: string;
props?: { [key: string]: any };
status?: StatusEnum;
title: string;
} & Modal<any>;
export const AddModalButton: FC<AddModalButtonProps> = ({ id, title, Component, style, status, props }) => {
const { openModal, registerModal } = useModal();
useEffect(() => {
registerModal({
Component,
id,
props,
style,
title,
export const AddModalButton: FC<AddModalButtonProps> = ({ status, title, ...props }) => {
const [openModal] = useModal(props.id, {
...props,
title: props.modalTitle,
});
}, []);
const handleClick = () => {
openModal(id);
};
return <Button label={title} onClick={handleClick} status={status} />;
return <Button label={title} onClick={openModal} status={status} />;
};

View file

@ -6,7 +6,7 @@ import { ModalId } from '../context';
import { useModal } from '../hooks';
export const Modals = () => {
const { modals, opened, closeModal } = useModal();
const [, { modals, opened, closeModal }] = useModal();
const handleCloseModal = (modalId: ModalId) => {
modals[modalId]?.props?.onClose?.();
@ -43,11 +43,11 @@ export const Modals = () => {
className="modal-container"
style={{
width: style?.width,
...(style?.overrideMaxWidth ? { maxWidth: 'initial' } : {})
...(style?.overrideMaxWidth ? { maxWidth: 'initial' } : {}),
}}>
<div className="modal-title">{title}</div>
<div className="modal-body" onClick={(e: MouseEvent) => e.stopPropagation()}>
<Component {...props} />
{Component && <Component {...props} />}
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@ import React, { createContext, FC, ReactNode, useEffect, useMemo, useState } fro
import { omit } from 'ramda';
export type Modal<P> = {
Component: FC<P>;
Component?: FC<P>;
id: string;
props?: Partial<P>;
style?: {
@ -29,7 +29,7 @@ export const ModalContext = createContext<ModalContextType>({
open: () => {},
close: () => {},
register: () => {},
unregister: () => {}
unregister: () => {},
});
export const ModalWrapper: FC<{ children: ReactNode }> = ({ children }) => {
@ -41,8 +41,8 @@ export const ModalWrapper: FC<{ children: ReactNode }> = ({ children }) => {
...modals,
[modal.id]: {
...modals[modal.id],
...modal
}
...modal,
},
});
};
@ -77,9 +77,9 @@ export const ModalWrapper: FC<{ children: ReactNode }> = ({ children }) => {
unregister,
register,
open,
close
close,
}),
[modals, opened]
[modals, opened],
);
return <ModalContext.Provider value={value}>{children}</ModalContext.Provider>;

View file

@ -2,22 +2,28 @@ import { useContext, useEffect } from 'react';
import { Modal, ModalContext } from './context';
export const useModal = <P>(modal?: Modal<P>) => {
export const useModal = <P>(id?: Modal<P>['id'], modal?: Omit<Modal<P>, 'id'>) => {
const { registered, close, open, opened, register, unregister } = useContext(ModalContext);
useEffect(() => {
if (modal) {
register(modal);
return () => unregister(modal.id);
if (id && modal) {
register({
id,
...modal,
});
return () => unregister(id);
}
}, []);
return {
return [
(modal?: Omit<Modal<P>, 'id'>) => open(id!, modal),
{
modals: registered,
opened: opened,
openModal: open,
closeModal: close,
registerModal: register,
unregisterModal: unregister,
};
},
] as const;
};

View file

@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useState } from 'react';
import { find, propEq, propOr } from 'ramda';
import { ButtonCursorIcon } from '@procyon/components/Icons/ButtonCursor';
import { ModalId } from '@procyon/components/Modal/context';
import { useModal } from '@procyon/components/Modal/hooks';
import { InputElementType } from '@procyon/types/form';
import { Option } from '@procyon/types/options';
@ -21,7 +20,10 @@ export type SelectBoxProps<V = Option> = Omit<
export const Selectbox: React.FC<SelectBoxProps> = (props) => {
const { value, options, onChange, name, label, autoComplete, ...others } = props;
const { openModal, closeModal, registerModal } = useModal();
const [openModal, { closeModal }] = useModal(name, {
title: label,
style: { width: 300 },
});
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
@ -42,10 +44,6 @@ export const Selectbox: React.FC<SelectBoxProps> = (props) => {
onChange?.(undefined, null);
};
const handleCloseModal = (id: ModalId) => {
closeModal(id);
};
const modalComponent = useCallback(
(props) => (
<SelectboxModal
@ -53,23 +51,19 @@ export const Selectbox: React.FC<SelectBoxProps> = (props) => {
name={name}
options={options}
selectedOption={selectedOption}
closeModal={handleCloseModal}
closeModal={closeModal}
onChange={handleOptionClick}
onRemove={handleOptionRemove}
{...props}
/>
),
[autoComplete, name, options, selectedOption]
[autoComplete, name, options, selectedOption],
);
const handleOpenModal = (): void => {
registerModal({
id: name,
title: label,
style: { width: 300 },
Component: modalComponent
openModal({
Component: modalComponent,
});
openModal(name);
};
return (

View file

@ -42,7 +42,7 @@ const TimeList: React.FC<TimeListProps> = ({ title, onChange, min, max, step, se
selectedCode={selectedCode}
scrollToCode={scrollToCode}
enableAutoScroll
className="h-60"
className="h-60 text-center"
/>
</>
);

View file

@ -29,8 +29,6 @@ export type TimePickerProps<V = Date> = Omit<
export const TimePicker: FC<TimePickerProps> = (props) => {
const { onChange, label, name, hour, minute, scrollToCode, ...others } = props;
const { openModal, registerModal } = useModal();
const [value, setValue] = useState<Date>();
useEffect(() => {
@ -42,15 +40,10 @@ export const TimePicker: FC<TimePickerProps> = (props) => {
onChange?.(value, null);
};
const handleOpenModal = (): void => {
registerModal({
id: name,
const [openModal] = useModal(name, {
title: label,
style: { width: 200 },
Component: modalComponent
style: { width: 220 },
});
openModal(name);
};
const modalComponent = useCallback(
(props) => (
@ -64,9 +57,13 @@ export const TimePicker: FC<TimePickerProps> = (props) => {
{...props}
/>
),
[value, scrollToCode, name, minute, hour]
[value, scrollToCode, name, minute, hour],
);
const handleOpenModal = () => {
openModal({ Component: modalComponent });
};
return (
<TextField
{...others}

View file

@ -33,7 +33,7 @@ const { base } = GridSizeEnum;
export const TimePickerModal: React.FC<TimePickerModalProps> = (props) => {
const { onChange, modalName, scrollToCode } = props;
const { closeModal } = useModal();
const [, { closeModal }] = useModal(props.modalName);
const [hour, setHour] = useState<number | null>();
const [minute, setMinute] = useState<number | null>();

View file

@ -15,7 +15,7 @@ const creator: PluginCreator = ({ addComponents, theme }) =>
borderRadius: theme('borderRadius.md'),
border: `1px solid ${theme('colors.list.item.border')}`,
'&-compact': {
padding: '1px 1px 1px 8px',
padding: '1px',
},
'&--focus': {
backgroundColor: theme('colors.focus.background'),

View file

@ -29,6 +29,9 @@ const creator: PluginCreator = ({ addComponents, theme }) =>
lineHeight: '24px',
padding: '0.875rem',
height: '54px',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
},
'&-cross': {
position: 'absolute',

View file

@ -37,24 +37,15 @@ return <AddModalButton />;
```tsx
import { useModal } from '@procyon/components/Modal/hooks';
const { registerModal } = useModal();
registerModal({
const [openModal] = useModal({
component: <div>Modal content</div>,
id: 'modal1',
props: { onClick: () => {} },
style: { width: '300px' },
title: 'Modal Title',
});
```
Then, call `openModal` hook.
```ts
import { openModal } from '@procyon/components/Modal/hooks';
const { openModal } = useModal();
openModal('modal1');
openModal();
```
<Canvas of={ModalStories.Hook} withToolbar />

View file

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import { Meta, StoryObj } from '@storybook/react';
@ -43,15 +43,11 @@ export const Hook: Story = {
name: 'Hook',
render: () => {
const Comp = () => {
const { registerModal, openModal } = useModal();
useEffect(() => {
registerModal({
const [openModal] = useModal('modal2', {
Component: () => <div>Hi there</div>,
id: 'modal2',
title: 'Modal Title',
});
}, []);
return <Button label="Open Modal" onClick={() => openModal('modal2')} />;
return <Button label="Open Modal" onClick={openModal} />;
};
return <Comp />;
},

View file

@ -25,10 +25,12 @@ export default {
tags: ['autodocs'],
decorators: [
(Story) => (
<div style={{ height: '600px' }}>
<ModalWrapper>
<Story />
<Modals />
</ModalWrapper>
</div>
),
],
} as Meta<typeof TimePicker>;
@ -37,7 +39,7 @@ const date = new Date();
export const Default: Story = {
args: {
name: 'datePicker',
name: 'timePicker1',
label: 'Choose time',
value: setHours(date, 20),
},
@ -45,14 +47,14 @@ export const Default: Story = {
export const WithoutLabel: Story = {
args: {
name: 'datePicker',
name: 'timePicker2',
value: setHours(date, 20),
},
};
export const RestrictedTimes: Story = {
args: {
name: 'datePicker',
name: 'timePicker3',
label: 'Choose time',
value: setHours(date, 20),
minute: {