mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-01 08:45:21 +03:00
feat(web): add minimal Dialog component
This commit is contained in:
parent
0f1bc40f00
commit
98873d42ba
@ -15,6 +15,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root" class="z-0"></div>
|
<div id="root" class="z-0"></div>
|
||||||
|
<div id="dialogs" class="z-0"></div>
|
||||||
<div id="menus" class="z-0"></div>
|
<div id="menus" class="z-0"></div>
|
||||||
<div id="tooltips" class="z-0"></div>
|
<div id="tooltips" class="z-0"></div>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
47
web/src/components/Dialog.jsx
Normal file
47
web/src/components/Dialog.jsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { h, Fragment } from 'preact';
|
||||||
|
import Button from './Button';
|
||||||
|
import Heading from './Heading';
|
||||||
|
import { createPortal } from 'preact/compat';
|
||||||
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
|
export default function Dialog({ actions = [], portalRootID = 'dialogs', title, text }) {
|
||||||
|
const portalRoot = portalRootID && document.getElementById(portalRootID);
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
setShow(true);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dialog = (
|
||||||
|
<Fragment>
|
||||||
|
<div
|
||||||
|
data-testid="scrim"
|
||||||
|
key="scrim"
|
||||||
|
className="absolute inset-0 z-10 flex justify-center items-center bg-black bg-opacity-40"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="modal"
|
||||||
|
className={`absolute rounded shadow-2xl bg-white dark:bg-gray-700 max-w-sm text-gray-900 dark:text-white transition-transform transition-opacity duration-75 transform scale-90 opacity-0 ${
|
||||||
|
show ? 'scale-100 opacity-100' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<Heading size="lg">{title}</Heading>
|
||||||
|
<p>{text}</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
|
||||||
|
{actions.map(({ color, text, onClick }, i) => (
|
||||||
|
<Button className="ml-2" color={color} key={i} onClick={onClick} type="text">
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return portalRoot ? createPortal(dialog, portalRoot) : dialog;
|
||||||
|
}
|
||||||
38
web/src/components/__tests__/Dialog.test.jsx
Normal file
38
web/src/components/__tests__/Dialog.test.jsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import Dialog from '../Dialog';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||||
|
|
||||||
|
describe('Dialog', () => {
|
||||||
|
let portal;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
portal = document.createElement('div');
|
||||||
|
portal.id = 'dialogs';
|
||||||
|
document.body.appendChild(portal);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
document.body.removeChild(portal);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders to a portal', async () => {
|
||||||
|
render(<Dialog title="Tacos" text="This is the dialog" />);
|
||||||
|
expect(screen.getByText('Tacos')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('modal').closest('#dialogs')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders action buttons', async () => {
|
||||||
|
const handleClick = jest.fn();
|
||||||
|
render(
|
||||||
|
<Dialog
|
||||||
|
actions={[
|
||||||
|
{ color: 'red', text: 'Delete' },
|
||||||
|
{ text: 'Okay', onClick: handleClick },
|
||||||
|
]}
|
||||||
|
title="Tacos"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Okay' }));
|
||||||
|
expect(handleClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -2,6 +2,7 @@ import { h } from 'preact';
|
|||||||
import ArrowDropdown from '../icons/ArrowDropdown';
|
import ArrowDropdown from '../icons/ArrowDropdown';
|
||||||
import ArrowDropup from '../icons/ArrowDropup';
|
import ArrowDropup from '../icons/ArrowDropup';
|
||||||
import Button from '../components/Button';
|
import Button from '../components/Button';
|
||||||
|
import Dialog from '../components/Dialog';
|
||||||
import Heading from '../components/Heading';
|
import Heading from '../components/Heading';
|
||||||
import Select from '../components/Select';
|
import Select from '../components/Select';
|
||||||
import Switch from '../components/Switch';
|
import Switch from '../components/Switch';
|
||||||
@ -10,6 +11,7 @@ import { useCallback, useState } from 'preact/hooks';
|
|||||||
|
|
||||||
export default function StyleGuide() {
|
export default function StyleGuide() {
|
||||||
const [switches, setSwitches] = useState({ 0: false, 1: true, 2: false, 3: false });
|
const [switches, setSwitches] = useState({ 0: false, 1: true, 2: false, 3: false });
|
||||||
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
|
||||||
const handleSwitch = useCallback(
|
const handleSwitch = useCallback(
|
||||||
(id, checked) => {
|
(id, checked) => {
|
||||||
@ -18,6 +20,10 @@ export default function StyleGuide() {
|
|||||||
[switches]
|
[switches]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDismissDialog = () => {
|
||||||
|
setShowDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Heading size="md">Button</Heading>
|
<Heading size="md">Button</Heading>
|
||||||
@ -59,6 +65,26 @@ export default function StyleGuide() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Heading size="md">Dialog</Heading>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShowDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show Dialog
|
||||||
|
</Button>
|
||||||
|
{showDialog ? (
|
||||||
|
<Dialog
|
||||||
|
onDismiss={handleDismissDialog}
|
||||||
|
title="This is a dialog"
|
||||||
|
text="Would you like to see more?"
|
||||||
|
actions={[
|
||||||
|
{ text: 'Yes', color: 'red', onClick: handleDismissDialog },
|
||||||
|
{ text: 'No', onClick: handleDismissDialog },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Heading size="md">Switch</Heading>
|
<Heading size="md">Switch</Heading>
|
||||||
<div className="flex-col space-y-4 max-w-4xl">
|
<div className="flex-col space-y-4 max-w-4xl">
|
||||||
<Switch label="Disabled, off" labelPosition="after" />
|
<Switch label="Disabled, off" labelPosition="after" />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user