19 changed files with 388 additions and 259 deletions
@ -6,13 +6,13 @@ specifiers: |
|||||
'@heroicons/react': ^2.0.13 |
'@heroicons/react': ^2.0.13 |
||||
'@hookform/error-message': ^2.0.1 |
'@hookform/error-message': ^2.0.1 |
||||
'@hookform/resolvers': ^2.9.10 |
'@hookform/resolvers': ^2.9.10 |
||||
'@meshtastic/meshtasticjs': ^0.7.2 |
'@meshtastic/meshtasticjs': ^0.7.3 |
||||
'@tailwindcss/forms': ^0.5.3 |
'@tailwindcss/forms': ^0.5.3 |
||||
'@tailwindcss/line-clamp': ^0.4.2 |
'@tailwindcss/line-clamp': ^0.4.2 |
||||
'@tailwindcss/typography': ^0.5.8 |
'@tailwindcss/typography': ^0.5.8 |
||||
'@types/chrome': ^0.0.203 |
'@types/chrome': ^0.0.204 |
||||
'@types/geodesy': ^2.2.3 |
'@types/geodesy': ^2.2.3 |
||||
'@types/node': ^18.11.12 |
'@types/node': ^18.11.13 |
||||
'@types/react': ^18.0.26 |
'@types/react': ^18.0.26 |
||||
'@types/react-dom': ^18.0.9 |
'@types/react-dom': ^18.0.9 |
||||
'@types/w3c-web-serial': ^1.0.3 |
'@types/w3c-web-serial': ^1.0.3 |
||||
@ -23,7 +23,7 @@ specifiers: |
|||||
autoprefixer: ^10.4.13 |
autoprefixer: ^10.4.13 |
||||
base64-js: ^1.5.1 |
base64-js: ^1.5.1 |
||||
chart.js: ^4.0.1 |
chart.js: ^4.0.1 |
||||
chartjs-adapter-date-fns: ^2.0.1 |
chartjs-adapter-date-fns: ^3.0.0 |
||||
class-transformer: ^0.5.1 |
class-transformer: ^0.5.1 |
||||
class-validator: ^0.14.0 |
class-validator: ^0.14.0 |
||||
date-fns: ^2.29.3 |
date-fns: ^2.29.3 |
||||
@ -69,12 +69,12 @@ dependencies: |
|||||
'@heroicons/react': 2.0[email protected] |
'@heroicons/react': 2.0[email protected] |
||||
'@hookform/error-message': 2.0.1_nrnvpvixg5xdweezd67llqjwze |
'@hookform/error-message': 2.0.1_nrnvpvixg5xdweezd67llqjwze |
||||
'@hookform/resolvers': 2.9[email protected] |
'@hookform/resolvers': 2.9[email protected] |
||||
'@meshtastic/meshtasticjs': 0.7.2 |
'@meshtastic/meshtasticjs': 0.7.3 |
||||
'@tailwindcss/line-clamp': 0.4[email protected] |
'@tailwindcss/line-clamp': 0.4[email protected] |
||||
'@tailwindcss/typography': 0.5[email protected] |
'@tailwindcss/typography': 0.5[email protected] |
||||
base64-js: 1.5.1 |
base64-js: 1.5.1 |
||||
chart.js: 4.0.1 |
chart.js: 4.0.1 |
||||
chartjs-adapter-date-fns: 2.0[email protected] |
chartjs-adapter-date-fns: 3.0.0_thp3sedjxvmiqxrdsmekdnimom |
||||
class-transformer: 0.5.1 |
class-transformer: 0.5.1 |
||||
class-validator: 0.14.0 |
class-validator: 0.14.0 |
||||
date-fns: 2.29.3 |
date-fns: 2.29.3 |
||||
@ -98,9 +98,9 @@ dependencies: |
|||||
|
|
||||
devDependencies: |
devDependencies: |
||||
'@tailwindcss/forms': 0.5[email protected] |
'@tailwindcss/forms': 0.5[email protected] |
||||
'@types/chrome': 0.0.203 |
'@types/chrome': 0.0.204 |
||||
'@types/geodesy': 2.2.3 |
'@types/geodesy': 2.2.3 |
||||
'@types/node': 18.11.12 |
'@types/node': 18.11.13 |
||||
'@types/react': 18.0.26 |
'@types/react': 18.0.26 |
||||
'@types/react-dom': 18.0.9 |
'@types/react-dom': 18.0.9 |
||||
'@types/w3c-web-serial': 1.0.3 |
'@types/w3c-web-serial': 1.0.3 |
||||
@ -125,7 +125,7 @@ devDependencies: |
|||||
tslib: 2.4.1 |
tslib: 2.4.1 |
||||
typescript: 4.9.4 |
typescript: 4.9.4 |
||||
unimported: 1.23.0 |
unimported: 1.23.0 |
||||
vite: 4.0.0_@[email protected]2 |
vite: 4.0.0_@[email protected]3 |
||||
vite-plugin-environment: 1.1[email protected] |
vite-plugin-environment: 1.1[email protected] |
||||
|
|
||||
packages: |
packages: |
||||
@ -785,8 +785,8 @@ packages: |
|||||
engines: {node: '>=6.0.0'} |
engines: {node: '>=6.0.0'} |
||||
dev: false |
dev: false |
||||
|
|
||||
/@meshtastic/meshtasticjs/0.7.2: |
/@meshtastic/meshtasticjs/0.7.3: |
||||
resolution: {integrity: sha512-RJCJLtlGUn7To+I4MnxuSDxwWK/MgAAfbfq+Naib6fve2VQEBu2CPF2Iy0A+wAHxR1ygOKcS8VbyS2D1FAQhcw==} |
resolution: {integrity: sha512-9NvWvTQSqCRNhYrE7FnhW9/bVSmnZ9LYGy04P+8IwhQzlYduhnaIb5vDKgLSURl5YhpnHlWOQFJVXs8tuZv2CQ==} |
||||
dependencies: |
dependencies: |
||||
'@protobuf-ts/runtime': 2.8.2 |
'@protobuf-ts/runtime': 2.8.2 |
||||
glob: 8.0.3 |
glob: 8.0.3 |
||||
@ -887,8 +887,8 @@ packages: |
|||||
resolution: {integrity: sha512-oYO/U4VD1DavwrKuCSQWdLG+5K22SLPem2OQaHmFcQuwHoVeGC+JGVRji2MUqZUAIQZHEonOeVfAX09hYiLsdg==} |
resolution: {integrity: sha512-oYO/U4VD1DavwrKuCSQWdLG+5K22SLPem2OQaHmFcQuwHoVeGC+JGVRji2MUqZUAIQZHEonOeVfAX09hYiLsdg==} |
||||
dev: false |
dev: false |
||||
|
|
||||
/@types/chrome/0.0.203: |
/@types/chrome/0.0.204: |
||||
resolution: {integrity: sha512-JlQNebwpBETVc8U1Rr2inDFuOTtn0lahRAhnddx1dd0S5RrLAFJEEsyIu7AXI14mkCgSunksnuLGioH8kvBqRA==} |
resolution: {integrity: sha512-EvnHfxMHUWP5EAlRMK66uIEUiy36t72vg5RwmzQv9tdIl2ZmAp92NwvmEZJKpbRnIMTEc2BmSmtrFiEISUJ0Sw==} |
||||
dependencies: |
dependencies: |
||||
'@types/filesystem': 0.0.32 |
'@types/filesystem': 0.0.32 |
||||
'@types/har-format': 1.2.10 |
'@types/har-format': 1.2.10 |
||||
@ -946,8 +946,8 @@ packages: |
|||||
'@types/pbf': 3.0.2 |
'@types/pbf': 3.0.2 |
||||
dev: false |
dev: false |
||||
|
|
||||
/@types/node/18.11.12: |
/@types/node/18.11.13: |
||||
resolution: {integrity: sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==} |
resolution: {integrity: sha512-IASpMGVcWpUsx5xBOrxMj7Bl8lqfuTY7FKAnPmu5cHkfQVWF8GulWS1jbRqA934qZL35xh5xN/+Xe/i26Bod4w==} |
||||
dev: true |
dev: true |
||||
|
|
||||
/@types/normalize-package-data/2.4.1: |
/@types/normalize-package-data/2.4.1: |
||||
@ -1153,7 +1153,7 @@ packages: |
|||||
'@babel/plugin-transform-react-jsx-source': 7.19.6_@[email protected] |
'@babel/plugin-transform-react-jsx-source': 7.19.6_@[email protected] |
||||
magic-string: 0.27.0 |
magic-string: 0.27.0 |
||||
react-refresh: 0.14.0 |
react-refresh: 0.14.0 |
||||
vite: 4.0.0_@[email protected]2 |
vite: 4.0.0_@[email protected]3 |
||||
transitivePeerDependencies: |
transitivePeerDependencies: |
||||
- supports-color |
- supports-color |
||||
dev: true |
dev: true |
||||
@ -1435,12 +1435,14 @@ packages: |
|||||
engines: {pnpm: ^7.0.0} |
engines: {pnpm: ^7.0.0} |
||||
dev: false |
dev: false |
||||
|
|
||||
/chartjs-adapter-date-fns/[email protected]: |
/chartjs-adapter-date-fns/3.0.0_thp3sedjxvmiqxrdsmekdnimom: |
||||
resolution: {integrity: sha512-v3WV9rdnQ05ce3A0ZCjzUekJCAbfm6+3HqSoeY2BIkdMYZoYr/4T+ril1tZyDl869lz6xdNVMXejUFT9YKpw4A==} |
resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==} |
||||
peerDependencies: |
peerDependencies: |
||||
chart.js: '>=2.8.0' |
chart.js: '>=2.8.0' |
||||
|
date-fns: '>=2.0.0' |
||||
dependencies: |
dependencies: |
||||
chart.js: 4.0.1 |
chart.js: 4.0.1 |
||||
|
date-fns: 2.29.3 |
||||
dev: false |
dev: false |
||||
|
|
||||
/chokidar/3.5.3: |
/chokidar/3.5.3: |
||||
@ -3758,8 +3760,8 @@ packages: |
|||||
yargs: 17.6.2 |
yargs: 17.6.2 |
||||
dev: true |
dev: true |
||||
|
|
||||
/rollup/3.7.1: |
/rollup/3.7.2: |
||||
resolution: {integrity: sha512-ek6+FORvI79VQTNlIYtXpIrGEPRlYSNZO+5EcmaozKkRL5L6KLvGDUbM5E+bd6jnHW9fgcK0DKTdWjIsEmNb4g==} |
resolution: {integrity: sha512-orqIX5zkHyHKVsIBl8J5a2tnVikOAMte0DgOLViyW6McYuj45FG+cQPrXILhaifBSmy0D0hKbHg2RbgzFJcwTg==} |
||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'} |
engines: {node: '>=14.18.0', npm: '>=8.0.0'} |
||||
hasBin: true |
hasBin: true |
||||
optionalDependencies: |
optionalDependencies: |
||||
@ -4288,10 +4290,10 @@ packages: |
|||||
peerDependencies: |
peerDependencies: |
||||
vite: '>= 2.7' |
vite: '>= 2.7' |
||||
dependencies: |
dependencies: |
||||
vite: 4.0.0_@[email protected]2 |
vite: 4.0.0_@[email protected]3 |
||||
dev: true |
dev: true |
||||
|
|
||||
/vite/4.0.0_@[email protected]2: |
/vite/4.0.0_@[email protected]3: |
||||
resolution: {integrity: sha512-ynad+4kYs8Jcnn8J7SacS9vAbk7eMy0xWg6E7bAhS1s79TK+D7tVFGXVZ55S7RNLRROU1rxoKlvZ/qjaB41DGA==} |
resolution: {integrity: sha512-ynad+4kYs8Jcnn8J7SacS9vAbk7eMy0xWg6E7bAhS1s79TK+D7tVFGXVZ55S7RNLRROU1rxoKlvZ/qjaB41DGA==} |
||||
engines: {node: ^14.18.0 || >=16.0.0} |
engines: {node: ^14.18.0 || >=16.0.0} |
||||
hasBin: true |
hasBin: true |
||||
@ -4316,11 +4318,11 @@ packages: |
|||||
terser: |
terser: |
||||
optional: true |
optional: true |
||||
dependencies: |
dependencies: |
||||
'@types/node': 18.11.12 |
'@types/node': 18.11.13 |
||||
esbuild: 0.16.4 |
esbuild: 0.16.4 |
||||
postcss: 8.4.19 |
postcss: 8.4.19 |
||||
resolve: 1.22.1 |
resolve: 1.22.1 |
||||
rollup: 3.7.1 |
rollup: 3.7.2 |
||||
optionalDependencies: |
optionalDependencies: |
||||
fsevents: 2.3.2 |
fsevents: 2.3.2 |
||||
dev: true |
dev: true |
||||
|
|||||
@ -1,125 +0,0 @@ |
|||||
import type React from "react"; |
|
||||
import { useEffect, useState } from "react"; |
|
||||
|
|
||||
import { fromByteArray } from "base64-js"; |
|
||||
import { toast } from "react-hot-toast"; |
|
||||
import { QRCode } from "react-qrcode-logo"; |
|
||||
|
|
||||
import { Checkbox } from "@components/form/Checkbox.js"; |
|
||||
import { IconButton } from "@components/form/IconButton.js"; |
|
||||
import { Input } from "@components/form/Input.js"; |
|
||||
import { Dialog } from "@headlessui/react"; |
|
||||
import { ClipboardIcon, XMarkIcon } from "@heroicons/react/24/outline"; |
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs"; |
|
||||
|
|
||||
export interface ImportDialogProps { |
|
||||
isOpen: boolean; |
|
||||
close: () => void; |
|
||||
loraConfig?: Protobuf.Config_LoRaConfig; |
|
||||
channels: Protobuf.Channel[]; |
|
||||
} |
|
||||
|
|
||||
export const ImportDialog = ({ |
|
||||
isOpen, |
|
||||
close, |
|
||||
loraConfig, |
|
||||
channels |
|
||||
}: ImportDialogProps): JSX.Element => { |
|
||||
const [selectedChannels, setSelectedChannels] = useState<number[]>([0]); |
|
||||
const [QRCodeURL, setQRCodeURL] = useState<string>(""); |
|
||||
|
|
||||
useEffect(() => { |
|
||||
const channelsToEncode = channels |
|
||||
.filter((channel) => selectedChannels.includes(channel.index)) |
|
||||
.map((channel) => channel.settings) |
|
||||
.filter((ch): ch is Protobuf.ChannelSettings => !!ch); |
|
||||
const encoded = Protobuf.ChannelSet.toBinary({ |
|
||||
loraConfig, |
|
||||
settings: channelsToEncode |
|
||||
}); |
|
||||
const base64 = fromByteArray(encoded) |
|
||||
.replace(/=/g, "") |
|
||||
.replace(/\+/g, "-") |
|
||||
.replace(/\//g, "_"); |
|
||||
|
|
||||
setQRCodeURL(`https://meshtastic.org/e/#${base64}`); |
|
||||
}, [channels, selectedChannels, loraConfig]); |
|
||||
|
|
||||
return ( |
|
||||
<Dialog open={isOpen} onClose={close}> |
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" /> |
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4"> |
|
||||
<Dialog.Panel> |
|
||||
<div className="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow"> |
|
||||
<div className="flex px-4 py-5 sm:px-6"> |
|
||||
<div> |
|
||||
<h1 className="text-lg font-bold">Generate QR Code</h1> |
|
||||
<h5 className="text-sm text-slate-600"> |
|
||||
The current LoRa configuration will also be shared. |
|
||||
</h5> |
|
||||
</div> |
|
||||
<IconButton |
|
||||
onClick={close} |
|
||||
className="my-auto ml-auto" |
|
||||
size="sm" |
|
||||
variant="secondary" |
|
||||
icon={<XMarkIcon className="h-4" />} |
|
||||
/> |
|
||||
</div> |
|
||||
<div className="flex gap-3 px-4 py-5 sm:p-6"> |
|
||||
<div className="flex w-40 flex-col gap-1"> |
|
||||
{channels.map((channel) => ( |
|
||||
<Checkbox |
|
||||
key={channel.index} |
|
||||
disabled={ |
|
||||
channel.index === 0 || |
|
||||
channel.role === Protobuf.Channel_Role.DISABLED |
|
||||
} |
|
||||
label={ |
|
||||
channel.settings?.name.length |
|
||||
? channel.settings.name |
|
||||
: channel.role === Protobuf.Channel_Role.PRIMARY |
|
||||
? "Primary" |
|
||||
: `Channel: ${channel.index}` |
|
||||
} |
|
||||
checked={selectedChannels.includes(channel.index)} |
|
||||
onChange={() => { |
|
||||
if (selectedChannels.includes(channel.index)) { |
|
||||
setSelectedChannels( |
|
||||
selectedChannels.filter((c) => c !== channel.index) |
|
||||
); |
|
||||
} else { |
|
||||
setSelectedChannels([ |
|
||||
...selectedChannels, |
|
||||
channel.index |
|
||||
]); |
|
||||
} |
|
||||
}} |
|
||||
/> |
|
||||
))} |
|
||||
</div> |
|
||||
<QRCode value={QRCodeURL} size={200} qrStyle="dots" /> |
|
||||
</div> |
|
||||
|
|
||||
<div className="px-4 py-4 sm:px-6"> |
|
||||
<Input |
|
||||
label="Sharable URL" |
|
||||
value={QRCodeURL} |
|
||||
disabled |
|
||||
action={{ |
|
||||
icon: <ClipboardIcon className="h-4" />, |
|
||||
action() { |
|
||||
void navigator.clipboard.writeText(QRCodeURL); |
|
||||
toast.success("Copied URL to Clipboard"); |
|
||||
} |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* </Card> */} |
|
||||
</div> |
|
||||
</Dialog.Panel> |
|
||||
</div> |
|
||||
</Dialog> |
|
||||
); |
|
||||
}; |
|
||||
@ -0,0 +1,68 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useState } from "react"; |
||||
|
|
||||
|
import { useDevice } from "@app/core/providers/useDevice.js"; |
||||
|
import { Dialog } from "@components/generic/Dialog.js"; |
||||
|
import { ArrowPathIcon, ClockIcon } from "@heroicons/react/24/outline"; |
||||
|
|
||||
|
import { Button } from "../form/Button.js"; |
||||
|
import { Input } from "../form/Input.js"; |
||||
|
|
||||
|
export interface RebootDialogProps { |
||||
|
isOpen: boolean; |
||||
|
close: () => void; |
||||
|
} |
||||
|
|
||||
|
export const RebootDialog = ({ |
||||
|
isOpen, |
||||
|
close |
||||
|
}: RebootDialogProps): JSX.Element => { |
||||
|
const { connection, setRebootDialogOpen } = useDevice(); |
||||
|
|
||||
|
const [time, setTime] = useState<number>(5); |
||||
|
|
||||
|
return ( |
||||
|
<Dialog |
||||
|
title={"Schedule Reboot"} |
||||
|
description={"Reboot the connected node after x minutes."} |
||||
|
isOpen={isOpen} |
||||
|
close={close} |
||||
|
> |
||||
|
<div className="flex gap-2 p-4"> |
||||
|
<Input |
||||
|
type="number" |
||||
|
value={time} |
||||
|
onChange={(e) => setTime(parseInt(e.target.value))} |
||||
|
action={{ |
||||
|
icon: <ClockIcon className="w-4" />, |
||||
|
action() { |
||||
|
connection?.reboot({ |
||||
|
time: time * 60, |
||||
|
callback: async () => { |
||||
|
setRebootDialogOpen(false); |
||||
|
await Promise.resolve(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}} |
||||
|
/> |
||||
|
<Button |
||||
|
className="w-24" |
||||
|
variant="secondary" |
||||
|
iconAfter={<ArrowPathIcon className="w-4" />} |
||||
|
onClick={() => { |
||||
|
connection?.reboot({ |
||||
|
time: 0, |
||||
|
callback: async () => { |
||||
|
setRebootDialogOpen(false); |
||||
|
await Promise.resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}} |
||||
|
> |
||||
|
Now |
||||
|
</Button> |
||||
|
</div> |
||||
|
</Dialog> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,68 @@ |
|||||
|
import type React from "react"; |
||||
|
import { useState } from "react"; |
||||
|
|
||||
|
import { useDevice } from "@app/core/providers/useDevice.js"; |
||||
|
import { Dialog } from "@components/generic/Dialog.js"; |
||||
|
import { ClockIcon, PowerIcon } from "@heroicons/react/24/outline"; |
||||
|
|
||||
|
import { Button } from "../form/Button.js"; |
||||
|
import { Input } from "../form/Input.js"; |
||||
|
|
||||
|
export interface ShutdownDialogProps { |
||||
|
isOpen: boolean; |
||||
|
close: () => void; |
||||
|
} |
||||
|
|
||||
|
export const ShutdownDialog = ({ |
||||
|
isOpen, |
||||
|
close |
||||
|
}: ShutdownDialogProps): JSX.Element => { |
||||
|
const { connection, setShutdownDialogOpen } = useDevice(); |
||||
|
|
||||
|
const [time, setTime] = useState<number>(5); |
||||
|
|
||||
|
return ( |
||||
|
<Dialog |
||||
|
title={"Schedule Shutdown"} |
||||
|
description={"Turn off the connected node after x minutes."} |
||||
|
isOpen={isOpen} |
||||
|
close={close} |
||||
|
> |
||||
|
<div className="flex gap-2 p-4"> |
||||
|
<Input |
||||
|
type="number" |
||||
|
value={time} |
||||
|
onChange={(e) => setTime(parseInt(e.target.value))} |
||||
|
action={{ |
||||
|
icon: <ClockIcon className="w-4" />, |
||||
|
action() { |
||||
|
connection?.shutdown({ |
||||
|
time: time * 60, |
||||
|
callback: async () => { |
||||
|
setShutdownDialogOpen(false); |
||||
|
await Promise.resolve(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}} |
||||
|
/> |
||||
|
<Button |
||||
|
className="w-24" |
||||
|
variant="secondary" |
||||
|
iconAfter={<PowerIcon className="w-4" />} |
||||
|
onClick={() => { |
||||
|
connection?.shutdown({ |
||||
|
time: 0, |
||||
|
callback: async () => { |
||||
|
setShutdownDialogOpen(false); |
||||
|
await Promise.resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}} |
||||
|
> |
||||
|
Now |
||||
|
</Button> |
||||
|
</div> |
||||
|
</Dialog> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,48 @@ |
|||||
|
import type React from "react"; |
||||
|
|
||||
|
import { IconButton } from "@components/form/IconButton.js"; |
||||
|
import { Dialog as DialogUI } from "@headlessui/react"; |
||||
|
import { XMarkIcon } from "@heroicons/react/24/outline"; |
||||
|
|
||||
|
export interface DialogProps { |
||||
|
title: string; |
||||
|
description: string; |
||||
|
isOpen: boolean; |
||||
|
close: () => void; |
||||
|
children: React.ReactNode; |
||||
|
} |
||||
|
|
||||
|
export const Dialog = ({ |
||||
|
title, |
||||
|
description, |
||||
|
isOpen, |
||||
|
close, |
||||
|
children |
||||
|
}: DialogProps): JSX.Element => { |
||||
|
return ( |
||||
|
<DialogUI open={isOpen} onClose={close}> |
||||
|
<div className="fixed inset-0 bg-black/30" aria-hidden="true" /> |
||||
|
<div className="fixed inset-0 flex items-center justify-center p-4"> |
||||
|
<DialogUI.Panel> |
||||
|
<div className="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow"> |
||||
|
<div className="flex px-4 py-5 sm:px-6"> |
||||
|
<div> |
||||
|
<h1 className="text-lg font-bold">{title}</h1> |
||||
|
<h5 className="text-sm text-slate-600">{description}</h5> |
||||
|
</div> |
||||
|
<IconButton |
||||
|
onClick={close} |
||||
|
className="my-auto ml-auto" |
||||
|
size="sm" |
||||
|
variant="secondary" |
||||
|
icon={<XMarkIcon className="h-4" />} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div className="p-4">{children}</div> |
||||
|
</div> |
||||
|
</DialogUI.Panel> |
||||
|
</div> |
||||
|
</DialogUI> |
||||
|
); |
||||
|
}; |
||||
Loading…
Reference in new issue