diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 1b449ecb..6bd22f89 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -14,18 +14,21 @@ const FlashMessageRender = ({ byKey, className }: Props) => { )); return ( -
- { - flashes.map((flash, index) => ( - - {index > 0 &&
} - - {flash.message} - -
- )) - } -
+ flashes.length ? +
+ { + flashes.map((flash, index) => ( + + {index > 0 &&
} + + {flash.message} + +
+ )) + } +
+ : + null ); }; diff --git a/resources/scripts/components/elements/Checkbox.tsx b/resources/scripts/components/elements/Checkbox.tsx index bd7b7a70..ae7548dc 100644 --- a/resources/scripts/components/elements/Checkbox.tsx +++ b/resources/scripts/components/elements/Checkbox.tsx @@ -1,14 +1,15 @@ import React from 'react'; import { Field, FieldProps } from 'formik'; +import Input from '@/components/elements/Input'; interface Props { name: string; value: string; } -type OmitFields = 'name' | 'value' | 'type' | 'checked' | 'onChange'; +type OmitFields = 'ref' | 'name' | 'value' | 'type' | 'checked' | 'onClick' | 'onChange'; -type InputProps = Omit, HTMLInputElement>, OmitFields>; +type InputProps = Omit; const Checkbox = ({ name, value, ...props }: Props & InputProps) => ( @@ -20,7 +21,7 @@ const Checkbox = ({ name, value, ...props }: Props & InputProps) => ( } return ( - { return () => { document.removeEventListener('click', windowListener); - } + }; }, [ visible ]); return (
{renderToggle(onClickHandler)} - +
{ e.stopPropagation(); setVisible(false); }} - className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'} + css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500`} + style={{ minWidth: '12rem' }} > {children}
-
+
); }; diff --git a/resources/scripts/components/elements/Input.tsx b/resources/scripts/components/elements/Input.tsx index 129249ba..4615ab14 100644 --- a/resources/scripts/components/elements/Input.tsx +++ b/resources/scripts/components/elements/Input.tsx @@ -16,6 +16,25 @@ const light = css` } `; +const checkboxStyle = css` + ${tw`cursor-pointer appearance-none inline-block align-middle select-none flex-shrink-0 w-4 h-4 text-primary-400 border border-neutral-300 rounded-sm`}; + color-adjust: exact; + background-origin: border-box; + transition: all 75ms linear, box-shadow 25ms linear; + + &:checked { + ${tw`border-transparent bg-no-repeat bg-center`}; + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + background-color: currentColor; + background-size: 100% 100%; + } + + &:focus { + ${tw`outline-none border-primary-300`}; + box-shadow: 0 0 0 1px rgba(9, 103, 210, 0.25); + } +`; + const inputStyle = css` // Reset to normal styling. ${tw`appearance-none w-full min-w-0`}; @@ -43,7 +62,19 @@ const inputStyle = css` ${props => props.hasError && tw`text-red-600 border-red-500 hover:border-red-600`}; `; -const Input = styled.input`${inputStyle}`; +const Input = styled.input` + &:not([type="checkbox"]):not([type="radio"]) { + ${inputStyle}; + } + + &[type="checkbox"], &[type="radio"] { + ${checkboxStyle}; + + &[type="radio"] { + ${tw`rounded-full`}; + } + } +`; const Textarea = styled.textarea`${inputStyle}`; export { Textarea }; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index 0ef756d3..9e968b0d 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -27,6 +27,7 @@ const ModalMask = styled.div` const ModalContainer = styled.div<{ alignTop?: boolean }>` ${tw`relative flex flex-col w-full m-auto`}; + max-height: calc(100vh - 8rem); max-width: 50%; // @todo max-w-screen-lg perhaps? ${props => props.alignTop && 'margin-top: 10%'}; diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 1dbe3070..669f04e8 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -10,6 +10,7 @@ import FlashMessageRender from '@/components/FlashMessageRender'; import BackupRow from '@/components/server/backups/BackupRow'; import { ServerContext } from '@/state/server'; import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; export default () => { const { uuid, featureLimits } = useServer(); @@ -31,14 +32,14 @@ export default () => { }, []); if (backups.length === 0 && loading) { - return ; + return ; } return ( - + {!backups.length ? -

+

There are no backups stored for this server.

: @@ -46,7 +47,7 @@ export default () => { {backups.map((backup, index) => 0 ? tw`mt-2` : undefined} />)} } @@ -57,12 +58,12 @@ export default () => { } {(featureLimits.backups > 0 && backups.length > 0) && -

+

{backups.length} of {featureLimits.backups} backups have been created for this server.

} {featureLimits.backups > 0 && featureLimits.backups !== backups.length && -
+
} diff --git a/resources/scripts/components/server/backups/BackupContextMenu.tsx b/resources/scripts/components/server/backups/BackupContextMenu.tsx index 16b636e5..7d2def09 100644 --- a/resources/scripts/components/server/backups/BackupContextMenu.tsx +++ b/resources/scripts/components/server/backups/BackupContextMenu.tsx @@ -16,6 +16,7 @@ import deleteBackup from '@/api/server/backups/deleteBackup'; import { ServerContext } from '@/state/server'; import ConfirmationModal from '@/components/elements/ConfirmationModal'; import Can from '@/components/elements/Can'; +import tw from 'twin.macro'; interface Props { backup: ServerBackup; @@ -61,8 +62,8 @@ export default ({ backup }: Props) => { <> {visible && setVisible(false)} checksum={backup.sha256Hash} /> @@ -84,27 +85,27 @@ export default ({ backup }: Props) => { renderToggle={onClick => ( )} > -
+
doDownload()}> - - Download + + Download setVisible(true)}> - - Checksum + + Checksum - setDeleteVisible(true)}> - - Delete + setDeleteVisible(true)}> + + Delete
diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx index 8a4f4f62..3f96a7c7 100644 --- a/resources/scripts/components/server/backups/BackupRow.tsx +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -10,6 +10,8 @@ import useWebsocketEvent from '@/plugins/useWebsocketEvent'; import { ServerContext } from '@/state/server'; import BackupContextMenu from '@/components/server/backups/BackupContextMenu'; import { faEllipsisH } from '@fortawesome/free-solid-svg-icons/faEllipsisH'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; interface Props { backup: ServerBackup; @@ -34,38 +36,38 @@ export default ({ backup, className }: Props) => { }); return ( -
-
+ +
{backup.completedAt ? - + : }
-
-

+

+

{backup.name} {backup.completedAt && - {bytesToHuman(backup.bytes)} + {bytesToHuman(backup.bytes)} }

-

+

{backup.uuid}

-
+

{formatDistanceToNow(backup.createdAt, { includeSeconds: true, addSuffix: true })}

-

Created

+

Created

-
+
{!backup.completedAt ? -
+
: @@ -73,6 +75,6 @@ export default ({ backup, className }: Props) => { }
-
+ ); }; diff --git a/resources/scripts/components/server/backups/ChecksumModal.tsx b/resources/scripts/components/server/backups/ChecksumModal.tsx index f400da75..91b27590 100644 --- a/resources/scripts/components/server/backups/ChecksumModal.tsx +++ b/resources/scripts/components/server/backups/ChecksumModal.tsx @@ -1,14 +1,15 @@ import React from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; +import tw from 'twin.macro'; const ChecksumModal = ({ checksum, ...props }: RequiredModalProps & { checksum: string }) => ( -

Verify file checksum

-

+

Verify file checksum

+

The SHA256 checksum of this file is:

-
-            {checksum}
+        
+            {checksum}
         
); diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx index 9e0e8421..8da01dca 100644 --- a/resources/scripts/components/server/backups/CreateBackupButton.tsx +++ b/resources/scripts/components/server/backups/CreateBackupButton.tsx @@ -10,6 +10,9 @@ import createServerBackup from '@/api/server/backups/createServerBackup'; import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; import { ServerContext } from '@/state/server'; +import Button from '@/components/elements/Button'; +import tw from 'twin.macro'; +import Input, { Textarea } from '@/components/elements/Input'; interface Values { name: string; @@ -21,17 +24,17 @@ const ModalContent = ({ ...props }: RequiredModalProps) => { return ( -
- -

Create server backup

-
+ + +

Create server backup

+
-
+
{ prefixing the path with an exclamation point. `} > - +
-
- +
@@ -99,18 +95,15 @@ export default () => { })} > setVisible(false)} /> } - + ); }; diff --git a/resources/scripts/components/server/users/AddSubuserButton.tsx b/resources/scripts/components/server/users/AddSubuserButton.tsx index 10b30024..5903088c 100644 --- a/resources/scripts/components/server/users/AddSubuserButton.tsx +++ b/resources/scripts/components/server/users/AddSubuserButton.tsx @@ -2,20 +2,18 @@ import React, { useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUserPlus } from '@fortawesome/free-solid-svg-icons/faUserPlus'; import EditSubuserModal from '@/components/server/users/EditSubuserModal'; +import Button from '@/components/elements/Button'; +import tw from 'twin.macro'; export default () => { const [ visible, setVisible ] = useState(false); return ( <> - {visible && setVisible(false)} - />} - + {visible && setVisible(false)}/>} + ); }; diff --git a/resources/scripts/components/server/users/EditSubuserModal.tsx b/resources/scripts/components/server/users/EditSubuserModal.tsx index e37ca42d..3982d54e 100644 --- a/resources/scripts/components/server/users/EditSubuserModal.tsx +++ b/resources/scripts/components/server/users/EditSubuserModal.tsx @@ -18,6 +18,9 @@ import Can from '@/components/elements/Can'; import { usePermissions } from '@/plugins/usePermissions'; import { useDeepMemo } from '@/plugins/useDeepMemo'; import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import Label from '@/components/elements/Label'; +import Input from '@/components/elements/Input'; type Props = { subuser?: Subuser; @@ -72,17 +75,17 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr } return list.filter(key => loggedInPermissions.indexOf(key) >= 0); - }, [permissions, loggedInPermissions]); + }, [ permissions, loggedInPermissions ]); return ( -

+

{subuser ? `${canEditUser ? 'Modify' : 'View'} permissions for ${subuser.email}` : 'Create new subuser' } -

+ {(!user.rootAdmin && loggedInPermissions[0] !== '*') &&
@@ -108,8 +111,8 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr title={

{key}

- {canEditUser && editablePermissions.indexOf(key) >= 0 && - { if (e.currentTarget.checked) { @@ -133,7 +136,7 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr }
} - className={index !== 0 ? 'mt-4' : undefined} + css={index > 0 ? tw`mt-4` : undefined} >

{permissions[key].description} @@ -157,9 +160,7 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr />

- - {pkey} - + {permissions[key].keys[pkey].length > 0 &&

{permissions[key].keys[pkey]} @@ -173,9 +174,9 @@ const EditSubuserModal = forwardRef(({ subuser, ...pr

- +
diff --git a/resources/scripts/components/server/users/RemoveSubuserButton.tsx b/resources/scripts/components/server/users/RemoveSubuserButton.tsx index 28f58dc4..cac5a3e6 100644 --- a/resources/scripts/components/server/users/RemoveSubuserButton.tsx +++ b/resources/scripts/components/server/users/RemoveSubuserButton.tsx @@ -8,6 +8,7 @@ import deleteSubuser from '@/api/server/users/deleteSubuser'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; +import tw from 'twin.macro'; export default ({ subuser }: { subuser: Subuser }) => { const [ loading, setLoading ] = useState(false); @@ -38,7 +39,7 @@ export default ({ subuser }: { subuser: Subuser }) => { doDeletion()} onDismissed={() => setShowConfirmation(false)} @@ -50,7 +51,7 @@ export default ({ subuser }: { subuser: Subuser }) => {
+ ); }; diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index f0e17fd4..55f60b44 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -10,6 +10,7 @@ import getServerSubusers from '@/api/server/users/getServerSubusers'; import { httpErrorToHuman } from '@/api/http'; import Can from '@/components/elements/Can'; import PageContentBlock from '@/components/elements/PageContentBlock'; +import tw from 'twin.macro'; export default () => { const [ loading, setLoading ] = useState(true); @@ -43,15 +44,15 @@ export default () => { }, []); if (!subusers.length && (loading || !Object.keys(permissions).length)) { - return ; + return ; } return ( - + {!subusers.length ? -

- It looks like you don't have any subusers. +

+ It looks like you don't have any subusers.

: subusers.map(subuser => ( @@ -59,7 +60,7 @@ export default () => { )) } -
+