diff --git a/package-lock.json b/package-lock.json index cf35997..6de1ac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,14 @@ { - "name": "qrscout-preact", - "version": "0.0.0", + "name": "qrscout", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "qrscout-preact", - "version": "0.0.0", + "name": "qrscout", + "version": "2.0.0", "dependencies": { + "@heroicons/react": "^2.1.1", "immer": "^10.0.3", "next-themes": "^0.2.1", "preact": "^10.19.3", @@ -775,6 +776,14 @@ "node": ">=12" } }, + "node_modules/@heroicons/react": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.1.tgz", + "integrity": "sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA==", + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2523,9 +2532,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -3212,13 +3221,13 @@ "dev": true }, "node_modules/vite": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", - "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", + "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", "dev": true, "dependencies": { "esbuild": "^0.19.3", - "postcss": "^8.4.32", + "postcss": "^8.4.35", "rollup": "^4.2.0" }, "bin": { diff --git a/package.json b/package.json index 8ddf926..8c13c01 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@heroicons/react": "^2.1.1", "immer": "^10.0.3", "next-themes": "^0.2.1", "preact": "^10.19.3", diff --git a/src/components/QR/QRModal.tsx b/src/components/QR/QRModal.tsx index 3a925a5..4e831ee 100644 --- a/src/components/QR/QRModal.tsx +++ b/src/components/QR/QRModal.tsx @@ -1,9 +1,8 @@ -import { useMemo, useRef } from 'preact/hooks'; +import { useMemo } from 'preact/hooks'; import QRCode from 'qrcode.react'; -import { useOnClickOutside } from '../../hooks/useOnClickOutside'; import { getFieldValue, useQRScoutState } from '../../store/store'; +import { Modal } from '../core/Modal'; import { Config } from '../inputs/BaseInputProps'; -import { CloseButton } from './CloseButton'; import { PreviewText } from './PreviewText'; export interface QRModalProps { @@ -20,9 +19,7 @@ export function getQRCodeData(formData: Config): string { } export function QRModal(props: QRModalProps) { - const modalRef = useRef(null); const formData = useQRScoutState(state => state.formData); - useOnClickOutside(modalRef, props.onDismiss); const title = `${getFieldValue('robot')} - M${getFieldValue( 'matchNumber', @@ -30,26 +27,12 @@ export function QRModal(props: QRModalProps) { const qrCodeData = useMemo(() => getQRCodeData(formData), [formData]); return ( - <> - {props.show && ( - <> -
-
-
- - -

{title}

- -
-
- - )} - + +
+ +

{title}

+ +
+
); } diff --git a/src/components/Sections/CommitAndResetSection/CommitAndResetSection.tsx b/src/components/Sections/CommitAndResetSection/CommitAndResetSection.tsx index d7cd8bf..aa3670b 100644 --- a/src/components/Sections/CommitAndResetSection/CommitAndResetSection.tsx +++ b/src/components/Sections/CommitAndResetSection/CommitAndResetSection.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'preact/hooks'; import { useQRScoutState } from '../../../store/store'; +import { Section } from '../../core/Section'; import { CommitButton } from './CommitButton'; import { ResetButton } from './ResetButton'; @@ -23,12 +24,12 @@ export function CommitAndResetSection({ }, [formData]); return ( -
+
0} onClick={onCommit} /> -
+ ); } diff --git a/src/components/Sections/ConfigSection/ConfigSection.tsx b/src/components/Sections/ConfigSection/ConfigSection.tsx index 506f65f..88f4659 100644 --- a/src/components/Sections/ConfigSection/ConfigSection.tsx +++ b/src/components/Sections/ConfigSection/ConfigSection.tsx @@ -1,84 +1,24 @@ -import { - resetToDefaultConfig, - uploadConfig, - useQRScoutState, -} from '../../../store/store'; +import { Cog6ToothIcon } from '@heroicons/react/20/solid'; +import { useState } from 'preact/hooks'; import Button, { Variant } from '../../core/Button'; -import { Config } from '../../inputs/BaseInputProps'; -import { ThemeSelector } from './ThemeSelector'; - -/** - * Download a text file - * @param filename The name of the file - * @param text The text to put in the file - */ -function download(filename: string, text: string) { - var element = document.createElement('a'); - element.setAttribute( - 'href', - 'data:text/plain;charset=utf-8,' + encodeURIComponent(text), - ); - element.setAttribute('download', filename); - - element.style.display = 'none'; - document.body.appendChild(element); - - element.click(); - - document.body.removeChild(element); -} - -/** - * Download the current form data as a json file - * @param formData The form data to download - */ -function downloadConfig(formData: Config) { - const configDownload = { ...formData }; - - configDownload.sections.forEach(s => - s.fields.forEach(f => (f.value = undefined)), - ); - download('QRScout_config.json', JSON.stringify(configDownload)); -} +import { Section } from '../../core/Section'; +import { SettingsModal } from './SettingsModal'; export function ConfigSection() { - const formData = useQRScoutState(state => state.formData); - return ( -
- - - - + const [showModal, setShowModal] = useState(false); - -
+ return ( +
+
+ setShowModal(false)} /> + +
+
); } diff --git a/src/components/Sections/ConfigSection/SettingsModal.tsx b/src/components/Sections/ConfigSection/SettingsModal.tsx new file mode 100644 index 0000000..5861e7f --- /dev/null +++ b/src/components/Sections/ConfigSection/SettingsModal.tsx @@ -0,0 +1,92 @@ +import { + resetToDefaultConfig, + uploadConfig, + useQRScoutState, +} from '../../../store/store'; +import Button, { Variant } from '../../core/Button'; +import { Modal } from '../../core/Modal'; +import { Config } from '../../inputs/BaseInputProps'; +import { ThemeSelector } from './ThemeSelector'; + +export interface ModalProps { + show: boolean; + onDismiss?: () => void; +} + +/** + * Download a text file + * @param filename The name of the file + * @param text The text to put in the file + */ +function download(filename: string, text: string) { + var element = document.createElement('a'); + element.setAttribute( + 'href', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(text), + ); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +/** + * Download the current form data as a json file + * @param formData The form data to download + */ +function downloadConfig(formData: Config) { + const configDownload = { ...formData }; + + configDownload.sections.forEach(s => + s.fields.forEach(f => (f.value = undefined)), + ); + download('QRScout_config.json', JSON.stringify(configDownload)); +} + +export function SettingsModal(props: ModalProps) { + const formData = useQRScoutState(state => state.formData); + return ( + +
+ + + + + + +
+
+ ); +} diff --git a/src/components/Sections/FormSection.tsx b/src/components/Sections/FormSection.tsx index e8bc634..e2d8cd9 100644 --- a/src/components/Sections/FormSection.tsx +++ b/src/components/Sections/FormSection.tsx @@ -1,4 +1,5 @@ import { useQRScoutState } from '../../store/store'; +import { Section } from '../core/Section'; import { InputProps } from '../inputs/BaseInputProps'; import ConfigurableInput from '../inputs/ConfigurableInput'; import InputCard from '../inputs/InputCard'; @@ -11,29 +12,17 @@ export default function FormSection(props: SectionProps) { const formData = useQRScoutState(state => state.formData); const inputs = formData.sections.find(s => s.name === props.name)?.fields; return ( -
-
-

- {props.name} -

-
-
- {inputs?.map((e: InputProps) => ( - - - - ))} -
-
+
+ {inputs?.map((e: InputProps) => ( + + + + ))} +
); } diff --git a/src/components/core/Button.tsx b/src/components/core/Button.tsx index bc9764c..53b69fb 100644 --- a/src/components/core/Button.tsx +++ b/src/components/core/Button.tsx @@ -1,22 +1,26 @@ -import React from 'react' -import { classNames } from '../../util/classNames' +import React from 'react'; +import { classNames } from '../../util/classNames'; export interface ButtonProps - extends React.HTMLAttributes { - variant: Variant + extends Omit, 'icon'> { + variant: Variant; + icon?: React.ReactNode; } export enum Variant { Primary, Secondary, Danger, + Transparent, } const VARIANT_MAPS: Record = { [Variant.Primary]: 'bg-gray-700 hover:bg-gray-800 disabled:bg-gray-300', [Variant.Secondary]: 'bg-gray-500 hover:bg-gray-600 disabled:bg-gray-300', [Variant.Danger]: 'bg-red-500 hover:bg-red-700 disabled:bg-red-300', -} + [Variant.Transparent]: + 'bg-transparent hover:bg-gray-200 hover:text-gray-600 disabled:bg-gray-300', +}; export default function Button(props: ButtonProps) { return ( @@ -24,12 +28,15 @@ export default function Button(props: ButtonProps) { type="button" onClick={props.onClick} className={classNames( - 'focus:shadow-outline mx-2 rounded py-2 px-4 font-bold text-white focus:outline-none', - VARIANT_MAPS[props.variant] + 'focus:shadow-outline mx-2 rounded py-2 px-4 font-bold text-gray-800 dark:text-white focus:outline-none', + VARIANT_MAPS[props.variant], )} disabled={props.disabled} > - {props.children} +
+ {props.icon && props.icon} + {props.children} +
- ) + ); } diff --git a/src/components/QR/CloseButton.tsx b/src/components/core/CloseButton.tsx similarity index 88% rename from src/components/QR/CloseButton.tsx rename to src/components/core/CloseButton.tsx index 74fe3ff..46b3fd8 100644 --- a/src/components/QR/CloseButton.tsx +++ b/src/components/core/CloseButton.tsx @@ -1,11 +1,11 @@ export type CloseButtonProps = { - onClick: () => void; + onClick?: () => void; }; export function CloseButton(props: CloseButtonProps) { return (