This commit is contained in:
Sacha Weatherstone
2021-08-15 18:27:33 +10:00
parent 2940791b9e
commit 29fcc8051c
46 changed files with 1143 additions and 1531 deletions
-204
View File
@@ -1,204 +0,0 @@
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import Button from '@material-ui/core/Button';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import Switch from '@material-ui/core/Switch';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
let renderCount = 0;
const options = [
{
value: 'chocolate',
label: 'Chocolate',
},
{
value: 'strawberry',
label: 'Strawberry',
},
{
value: 'vanilla',
label: 'Vanilla',
},
];
const defaultValues = {
Native: '',
TextField: '',
Select: '',
ReactSelect: '',
Checkbox: false,
switch: false,
RadioGroup: '',
};
export const TestForm = () => {
const { handleSubmit, register, reset, control, watch } = useForm({
defaultValues,
mode: 'onChange',
});
renderCount++;
const data = watch();
return (
<div className="flex w-full max-w-screen-md justify-start items-start">
<form
className="w-1/2"
onSubmit={handleSubmit((data) => console.info(data))}
>
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
Native Input:
</Typography>
<input
className="border-1 outline-none rounded-8 p-8"
{...register('Native')}
/>
</div>
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
MUI Checkbox
</Typography>
<Controller
name="Checkbox"
control={control}
defaultValue={false}
render={({ field: { onChange, value } }) => (
<Checkbox
checked={value}
onChange={(ev) => onChange(ev.target.checked)}
/>
)}
/>
</div>
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
Radio Group
</Typography>
<Controller
render={({ field }) => (
<RadioGroup {...field} aria-label="gender" name="gender1">
<FormControlLabel
value="female"
control={<Radio />}
label="Female"
/>
<FormControlLabel
value="male"
control={<Radio />}
label="Male"
/>
</RadioGroup>
)}
name="RadioGroup"
control={control}
/>
</div>
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
MUI TextField
</Typography>
<Controller
render={({ field }) => <TextField {...field} variant="outlined" />}
name="TextField"
control={control}
/>
</div>
{/* <div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
MUI Select
</Typography>
<Controller
render={({ field }) => (
<Select {...field} variant="outlined">
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
)}
name="Select"
control={control}
/>
</div> */}
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
MUI Switch
</Typography>
<Controller
name="switch"
control={control}
defaultValue={false}
render={({ field: { onChange, value } }) => (
<Switch
checked={value}
onChange={(ev) => onChange(ev.target.checked)}
/>
)}
/>
</div>
<div className="mt-48 mb-16">
<Typography className="mb-24 font-medium text-14">
React Select
</Typography>
{/* <Controller
render={({ field }) => <ReactSelect {...field} />}
options={options}
name="ReactSelect"
isClearable
control={control}
onChange={([selected]) => {
return { value: selected };
}}
/> */}
</div>
<div className="flex my-48 items-center">
<Button
className="mx-8"
variant="contained"
color="secondary"
type="submit"
>
Submit
</Button>
<Button
className="mx-8"
type="button"
onClick={() => {
reset(defaultValues);
}}
>
Reset Form
</Button>
</div>
</form>
<div className="w-1/2 my-48 p-24">
<pre className="language-js p-24 w-400">
{JSON.stringify(data, null, 2)}
</pre>
<Typography
className="mt-16 font-medium text-12 italic"
color="textSecondary"
>
Render Count: {renderCount}
</Typography>
</div>
</div>
);
};
+8 -4
View File
@@ -19,9 +19,13 @@ export const Message = ({
}: MessageProps): JSX.Element => {
return (
<div
className={`flex space-x-2 ${!isSender && 'ml-auto flex-row-reverse'}`}
className={`flex space-x-2 ${
!isSender ? 'ml-auto flex-row-reverse' : ''
}`}
>
<div className={`shadow-md rounded-full mt-auto ${!isSender && 'ml-2'}`}>
<div
className={`shadow-md rounded-full mt-auto ${!isSender ? 'ml-2' : ''}`}
>
<Avatar
size={30}
name={senderName ?? 'UNK'}
@@ -37,11 +41,11 @@ export const Message = ({
: 'bg-primary text-blue-50 rounded-bl-lg'
} ${ack ? 'animate-none' : 'animate-pulse'}`}
>
<div className="min-w-4 leading-5">{message}</div>
<div className="leading-5 min-w-4">{message}</div>
</div>
<div className="text-xs text-gray-600">{senderName}</div>
</div>
<div className="mt-auto mb-4 text-xs font-medium text-secondary mr-3 dark:text-gray-200">
<div className="mt-auto mb-4 mr-3 text-xs font-medium text-secondary dark:text-gray-200">
{rxTime.toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
+35 -37
View File
@@ -2,59 +2,57 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { Input } from '@components/generic/Input';
import { connection } from '@core/connection';
import {
EmojiHappyIcon,
PaperAirplaneIcon,
PaperClipIcon,
} from '@heroicons/react/outline';
import { connection } from '../../core/connection';
import { useAppSelector } from '../../hooks/redux';
import { IconButton } from '../generic/IconButton';
export const MessageBar = (): JSX.Element => {
const ready = useAppSelector((state) => state.meshtastic.ready);
const [currentMessage, setCurrentMessage] = React.useState('');
const sendMessage = () => {
const sendMessage = (): void => {
if (ready) {
connection.sendText(currentMessage, undefined, true);
void connection.sendText(currentMessage, undefined, true);
setCurrentMessage('');
}
};
const { t } = useTranslation();
return (
<div className="flex p-4 bg-gray-50 dark:bg-transparent space-x-2 text-gray-500 dark:text-gray-400">
<div className="flex">
<IconButton>
<EmojiHappyIcon className="w-6 h-6" />
</IconButton>
<IconButton>
<PaperClipIcon className="w-6 h-6" />
</IconButton>
</div>
<form
className="flex w-full space-x-2"
onSubmit={(e) => {
e.preventDefault();
sendMessage();
}}
>
<input
type="text"
minLength={2}
placeholder={`${t('placeholder.message')}...`}
disabled={!ready}
value={currentMessage}
onChange={(e) => {
setCurrentMessage(e.target.value);
<div className="flex w-full p-4 mx-auto space-x-2 text-gray-500 bg-gray-50 dark:bg-transparent dark:text-gray-400">
<div className="flex w-full max-w-4xl">
<div className="flex">
<Button icon={<EmojiHappyIcon className="w-5 h-5" />} circle />
<Button icon={<PaperClipIcon className="w-5 h-5" />} circle />
</div>
<form
className="flex w-full space-x-2"
onSubmit={(e): void => {
e.preventDefault();
sendMessage();
}}
className="focus:outline-none h-10 w-full resize-none rounded-full border border-gray-300 dark:bg-gray-900 px-4"
/>
<IconButton type="submit">
<PaperAirplaneIcon className="w-6 h-6 rotate-90" />
</IconButton>
</form>
>
<Input
type="text"
minLength={2}
placeholder={`${t('placeholder.message')}...`}
disabled={!ready}
value={currentMessage}
onChange={(e): void => {
setCurrentMessage(e.target.value);
}}
/>
<Button
icon={<PaperAirplaneIcon className="w-5 h-5" />}
type="submit"
circle
/>
</form>
</div>
</div>
);
};
-52
View File
@@ -1,52 +0,0 @@
import React from 'react';
type DefaultInputProps = JSX.IntrinsicElements['input'];
export interface InputProps {
valid?: boolean;
validationMessage?: string;
icon?: JSX.Element;
label: string;
}
export const Input = React.forwardRef<
HTMLInputElement,
InputProps & DefaultInputProps
>(function Input(
{
valid,
validationMessage,
icon,
label,
id,
...props
}: InputProps & DefaultInputProps,
ref,
) {
return (
<div className="space-y-1">
<label htmlFor={id} className="block text-sm font-medium dark:text-white">
{label}
</label>
<div className="relative">
{icon && (
<div className="flex absolute inset-y-0 left-0 px-3 items-center pointer-events-none">
{React.cloneElement(icon, {
className: 'w-5 h-5 text-gray-500 dark:text-gray-600',
})}
</div>
)}
<input
ref={ref}
{...props}
className={`block w-full h-11 rounded-md border shadow-sm focus:outline-none focus:border-primary dark:focus:border-primary dark:bg-secondaryDark dark:border-gray-600 dark:text-white ${
icon ? 'pl-9' : 'pl-2'
}`}
/>
</div>
{!valid && (
<div className="text-sm text-gray-600">{validationMessage}</div>
)}
</div>
);
});
-38
View File
@@ -1,38 +0,0 @@
import React from 'react';
import type { FieldValues, UseControllerProps } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import ReactSelect from 'react-select';
interface SelectProps<T> extends UseControllerProps<T> {
label: string;
options: {
value: string;
label: string;
}[];
}
export const Select = <T extends FieldValues>({
name,
control,
label,
options,
}: SelectProps<T>): JSX.Element => {
return (
<Controller
name={name}
control={control}
rules={{
required: 'This is required',
}}
render={({ field: { onChange, value, name } }) => (
<div className="space-y-1">
<span className="block text-sm font-medium dark:text-white">
{label}
</span>
<ReactSelect options={options} />
</div>
)}
/>
);
};
-40
View File
@@ -1,40 +0,0 @@
import React from 'react';
import type { FieldValues, UseControllerProps } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import MaterialSwitch from '@material-ui/core/Switch';
interface SwitchProps<T> extends UseControllerProps<T> {
label: string;
}
export const Switch = <T extends FieldValues>({
name,
control,
label,
}: SwitchProps<T>): JSX.Element => {
return (
<Controller
name={name}
control={control}
rules={{
required: 'This is required',
}}
render={({ field: { onChange, value, name } }) => (
<div className="flex flex-col">
<span className="block text-sm font-medium dark:text-white">
{label}
</span>
<div className="relative w-14 mr-2 ml-auto select-none">
<MaterialSwitch
id={name}
checked={value}
onChange={(ev) => onChange(ev.target.checked)}
/>
</div>
</div>
)}
/>
);
};
+33
View File
@@ -0,0 +1,33 @@
import React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface LocalBlurProps {
disableOnMd?: boolean;
}
export type BlurProps = LocalBlurProps & DefaultDivProps;
export const Blur = ({
disableOnMd,
className,
onClick,
...props
}: BlurProps): JSX.Element => {
return (
<div
className={`absolute inset-0 z-10 w-full h-full transition-opacity ${
disableOnMd ? 'md:hidden' : 'test'
} ${className}`}
{...props}
>
<div
onClick={onClick}
className={`absolute inset-0 w-full h-full backdrop-filter backdrop-blur-sm ${
disableOnMd ? 'md:hidden' : 'test'
}`}
tabIndex={0}
></div>
</div>
);
};
+37 -15
View File
@@ -1,28 +1,50 @@
import React from 'react';
import MaterialButton from '@material-ui/core/Button';
import type { ButtonProps as MaterialButtonProps } from '@material-ui/core/Button/Button';
type DefaultButtonProps = JSX.IntrinsicElements['button'];
interface LocalButtonProps {
text: string;
icon?: JSX.Element;
circle?: boolean;
active?: boolean;
border?: boolean;
}
export type ButtonProps = MaterialButtonProps & LocalButtonProps;
export type ButtonProps = LocalButtonProps & DefaultButtonProps;
export const Button = ({ text, icon, ...props }: ButtonProps): JSX.Element => {
export const Button = ({
icon,
circle,
className,
active,
border,
disabled,
children,
...props
}: ButtonProps): JSX.Element => {
return (
<MaterialButton
<button
className={`items-center select-none flex dark:text-white ${
active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : ''
} ${
circle ? 'rounded-full h-10 w-10' : 'rounded-md p-3 space-x-3 text-sm'
} ${
disabled
? 'cursor-not-allowed dark:bg-primaryDark bg-white'
: 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md'
} ${border ? 'border dark:border-gray-600' : ''} ${className}`}
{...props}
className="dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700"
>
<div className="flex p-3">
{icon &&
React.cloneElement(icon, {
className: 'h-6 w-6 mr-3 text-gray-500 dark:text-gray-400',
})}
<span>{text}</span>
</div>
</MaterialButton>
{icon && (
<div
className={`text-gray-500 dark:text-gray-400 ${
circle ? 'mx-auto' : ''
}`}
>
{icon}
</div>
)}
<span>{children}</span>
</button>
);
};
+35
View File
@@ -0,0 +1,35 @@
import React from 'react';
import { Blur } from '@components/generic/Blur';
type DefaultAsideProps = JSX.IntrinsicElements['aside'];
interface LocalDrawerProps {
open: boolean;
permenant?: boolean;
onClose: () => void;
}
export type DrawerProps = LocalDrawerProps & DefaultAsideProps;
export const Drawer = ({
open,
permenant,
onClose,
children,
...props
}: DrawerProps): JSX.Element => {
return (
<>
{open && <Blur disableOnMd={true} onClick={onClose} />}
<aside
className={`transform top-0 left-0 bg-white dark:bg-secondaryDark shadow-md max-w-xs w-full border-r dark:border-gray-600 h-full overflow-auto ease-in-out transition-all duration-300 z-30 ${
permenant ? '' : 'absolute'
} ${open ? 'translate-x-0' : '-translate-x-full'}`}
{...props}
>
{children}
</aside>
</>
);
};
-15
View File
@@ -1,15 +0,0 @@
import React from 'react';
import MaterialIconButton from '@material-ui/core/IconButton';
import type { IconButtonProps } from '@material-ui/core/IconButton/IconButton';
export const IconButton = ({
children,
...props
}: IconButtonProps): JSX.Element => {
return (
<MaterialIconButton {...props} className="text-gray-500 dark:text-gray-400">
{children}
</MaterialIconButton>
);
};
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
type DefaultInputProps = JSX.IntrinsicElements['input'];
interface LocalInputProps {
icon?: JSX.Element;
label?: string;
valid?: boolean;
validationMessage?: string;
}
export type InputProps = LocalInputProps & DefaultInputProps;
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input(
{ icon, label, valid, validationMessage, id, ...props }: InputProps,
ref,
) {
return (
<div className="w-full">
<label
htmlFor={id}
className="block text-sm font-medium dark:text-white"
>
{label}
</label>
<div className="relative">
{icon && (
<div className="absolute inset-y-0 left-0 flex items-center px-3 pointer-events-none">
{React.cloneElement(icon, {
className: 'w-5 h-5 text-gray-500 dark:text-gray-600',
})}
</div>
)}
<input
id={id}
ref={ref}
{...props}
className={`block w-full h-11 rounded-md border shadow-sm focus:outline-none focus:border-primary dark:focus:border-primary bg-white dark:bg-secondaryDark dark:border-gray-600 dark:text-white ${
icon ? 'pl-9' : 'pl-2'
}`}
/>
</div>
{!valid && (
<div className="text-sm text-gray-600">{validationMessage}</div>
)}
</div>
);
},
);
+82
View File
@@ -0,0 +1,82 @@
import React from 'react';
import { Listbox } from '@headlessui/react';
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
export interface SelectProps {
label: string;
options: {
name: string;
value: string;
icon: JSX.Element;
}[];
id: string;
value: string;
onChange: (value: string) => void;
}
export const Select = ({
label,
options,
id,
value,
onChange,
}: SelectProps): JSX.Element => {
return (
<div className="w-full">
<label htmlFor={id} className="block text-sm font-medium dark:text-white">
{label}
</label>
<Listbox value={value} onChange={onChange}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full text-left bg-white border rounded-md shadow-sm h-11 focus:outline-none focus:border-primary dark:focus:border-primary dark:bg-secondaryDark dark:border-gray-600 dark:text-white">
<span className="block truncate">{value}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Listbox.Options className="absolute w-full bg-white border rounded-md shadow-sm focus:outline-none dark:bg-secondaryDark dark:border-gray-600 dark:text-white">
{options.map((option) => (
<Listbox.Option
key={option.value}
className={({ active }): string =>
`cursor-default select-none relative py-2 pl-10 pr-4 first:rounded-t-md last:rounded-b-md dark:text-white ${
active ? 'bg-gray-200 dark:bg-primaryDark' : 'text-gray-900'
}`
}
value={option.value}
>
{({ selected, active }): JSX.Element => (
<>
<span
className={`${
selected ? 'font-medium' : 'font-normal'
} block truncate`}
>
{option.name}
</span>
{selected ? (
<span
className={`${
active ? 'text-amber-600' : 'text-amber-600'
}
absolute inset-y-0 left-0 flex items-center pl-3`}
>
<CheckIcon className="w-5 h-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</Listbox>
</div>
);
};
+33
View File
@@ -0,0 +1,33 @@
import React from 'react';
type DefaultDivProps = JSX.IntrinsicElements['div'];
interface LocalSidebarItemProps {
title: string;
description: string;
selected: boolean;
icon: JSX.Element;
}
export type SidebarItemProps = LocalSidebarItemProps & DefaultDivProps;
export const SidebarItem = ({
title,
description,
selected,
icon,
}: SidebarItemProps): JSX.Element => {
return (
<div
className={`flex p-5 cursor-pointer select-none dark:hover:bg-primaryDark ${
selected ? 'bg-gray-200 dark:bg-primaryDark' : 'dark:bg-secondaryDark'
}`}
>
<div className="text-gray-500 dark:text-gray-400">{icon}</div>
<div className="ml-3 text-left">
<div className="font-medium text-left">{title}</div>
<div className="mt-0.5 text-gray-400 text-sm">{description}</div>
</div>
</div>
);
};
+10 -2
View File
@@ -3,8 +3,16 @@ import React from 'react';
export const Logo = (): JSX.Element => {
return (
<>
<img className="w-16 dark:hidden" src="Mesh_Logo_Black.svg" />
<img className="hidden dark:flex w-16" src="Mesh_Logo_White.svg" />
<img
title="Logo"
className="w-16 dark:hidden"
src="Mesh_Logo_Black.svg"
/>
<img
title="Logo"
className="hidden w-16 dark:flex"
src="Mesh_Logo_White.svg"
/>
</>
);
};
-37
View File
@@ -1,37 +0,0 @@
import React from 'react';
import type { Link } from 'type-route';
interface MenuButtonProps {
icon: JSX.Element;
text: string;
link: Link;
clickAction?: () => void;
}
export const MenuButton = ({
icon,
text,
link,
clickAction,
}: MenuButtonProps): JSX.Element => {
return (
<div
onClick={() => {
if (clickAction) {
clickAction();
}
}}
>
<a
{...link}
className="flex text-sm h-12 items-center dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md cursor-pointer px-3 select-none"
>
{React.cloneElement(icon, {
className: 'h-6 w-6 mr-3 text-gray-500 dark:text-gray-400',
})}
<span className="">{text}</span>
</a>
</div>
);
};
+12 -38
View File
@@ -1,18 +1,11 @@
import React from 'react';
import {
AnnotationIcon,
CogIcon,
InformationCircleIcon,
ViewGridIcon,
} from '@heroicons/react/outline';
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer/SwipeableDrawer';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Drawer } from '@components/generic/Drawer';
import { closeMobileNav } from '@core/slices/appSlice';
import { routes } from '../../core/router';
import { closeMobileNav, openMobileNav } from '../../core/slices/appSlice';
import { useAppDispatch, useAppSelector } from '../../hooks/redux';
import { Button } from '../generic/Button';
import { Logo } from './Logo';
import { Navigation } from './Navigation';
export const MobileNav = (): JSX.Element => {
const dispatch = useAppDispatch();
@@ -20,41 +13,22 @@ export const MobileNav = (): JSX.Element => {
const mobileNavOpen = useAppSelector((state) => state.app.mobileNavOpen);
return (
<SwipeableDrawer
<Drawer
open={mobileNavOpen}
anchor="left"
onClose={() => {
onClose={(): void => {
dispatch(closeMobileNav());
}}
onOpen={() => {
dispatch(openMobileNav());
}}
>
<div className="flex flex-col dark:bg-secondaryDark h-full">
<div className="flex flex-col w-64">
<div className="m-auto my-6">
<Logo />
</div>
<Button
icon={<AnnotationIcon />}
text={'Messages'}
{...routes.messages().link}
/>
<Button
icon={<ViewGridIcon />}
text={'Nodes'}
{...routes.nodes().link}
/>
<Button
icon={<CogIcon />}
text={'Settings'}
{...routes.settings().link}
/>
<Button
icon={<InformationCircleIcon />}
text={'About'}
{...routes.about().link}
<Navigation
onClick={(): void => {
dispatch(closeMobileNav());
}}
/>
</div>
</SwipeableDrawer>
</Drawer>
);
};
+46 -17
View File
@@ -1,5 +1,7 @@
import React from 'react';
import { Button } from '@components/generic/Button';
import { routes, useRoute } from '@core/router';
import {
AnnotationIcon,
CogIcon,
@@ -7,33 +9,60 @@ import {
ViewGridIcon,
} from '@heroicons/react/outline';
import { routes } from '../../core/router';
import { Button } from '../generic/Button';
type DefaultDivProps = JSX.IntrinsicElements['div'];
export const Navigation = (): JSX.Element => {
export type NavigationProps = DefaultDivProps;
export const Navigation = ({
onClick,
className,
...props
}: NavigationProps): JSX.Element => {
const route = useRoute();
return (
<div className="hidden md:flex flex-auto flex-0 relative items-center h-16 px-4 ">
<div className="flex items-center">
<div
className={`h-16 px-4 md:space-x-2 space-y-2 md:space-y-0 ${className}`}
{...props}
>
<div onClick={onClick}>
<Button
icon={<AnnotationIcon />}
text={'Messages'}
icon={<AnnotationIcon className="w-6 h-6" />}
active={route.name === 'messages'}
className="w-full md:w-auto"
{...routes.messages().link}
/>
>
Messages
</Button>
</div>
<div onClick={onClick}>
<Button
icon={<ViewGridIcon />}
text={'Nodes'}
icon={<ViewGridIcon className="w-6 h-6" />}
className="w-full md:w-auto"
active={route.name === 'nodes'}
{...routes.nodes().link}
/>
>
Nodes
</Button>
</div>
<div onClick={onClick}>
<Button
icon={<CogIcon />}
text={'Settings'}
icon={<CogIcon className="w-6 h-6" />}
className="w-full md:w-auto"
active={route.name === 'settings'}
{...routes.settings().link}
/>
>
Settings
</Button>
</div>
<div onClick={onClick}>
<Button
icon={<InformationCircleIcon />}
text={'About'}
icon={<InformationCircleIcon className="w-6 h-6" />}
className="w-full md:w-auto"
active={route.name === 'about'}
{...routes.about().link}
/>
>
About
</Button>
</div>
</div>
);
@@ -1,29 +1,20 @@
import React from 'react';
import { useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { SwitchVerticalIcon } from '@heroicons/react/outline';
import { useAppSelector } from '../../../hooks/redux';
import { IconButton } from '../../generic/IconButton';
export const DeviceStatusDropdown = (): JSX.Element => {
const ready = useAppSelector((state) => state.meshtastic.ready);
const deviceStatus = useAppSelector((state) => state.meshtastic.deviceStatus);
return (
<IconButton>
<SwitchVerticalIcon className={`h-6 w-6 ${!ready && 'animate-pulse'}`} />
{/* <div
className={`flex w-6 h-6 rounded-full animate-pulse shadow-md ${
deviceStatus <= Types.DeviceStatusEnum.DEVICE_DISCONNECTED
? 'bg-red-400 animate-pulse'
: deviceStatus <= Types.DeviceStatusEnum.DEVICE_CONFIGURING &&
!ready
? 'bg-yellow-400 animate-pulse'
: ready
? 'bg-green-400'
: 'bg-gray-400'
}`}
></div> */}
</IconButton>
<Button
icon={
<SwitchVerticalIcon
className={`h-6 w-6 ${!ready ? 'animate-pulse' : ''}`}
/>
}
circle
/>
);
};
@@ -1,66 +0,0 @@
import React from 'react';
import { Jp, Pt, Us } from 'react-flags-select';
import { Menu } from '@headlessui/react';
import i18n from '../../../core/translation';
import { useAppDispatch } from '../../../hooks/redux';
import { IconButton } from '../../generic/IconButton';
export const LanguageDropdown = (): JSX.Element => {
const dispatch = useAppDispatch();
const languages = [
{
name: 'English',
value: 'en',
flag: <Us className="w-6" />,
},
{
name: 'Português',
value: 'pt',
flag: <Pt className="w-6" />,
},
{
name: 'Japanese',
value: 'jp',
flag: <Jp className="w-6" />,
},
];
return (
<Menu as="div" className="w-10 h-10">
<div className="absolute">
<IconButton>
<Menu.Button as="div">
<Us className="w-6 shadow rounded-sm" />
</Menu.Button>
</IconButton>
<Menu.Items className="z-20 absolute right-0 bg-white dark:bg-secondaryDark border dark:border-gray-600 divide-y divide-gray-200 dark:divide-gray-600 rounded-md shadow-md focus:outline-none">
{languages.map((language, index) => (
<Menu.Item
key={index}
onClick={() => {
i18n.changeLanguage(language.value);
}}
>
{({ active }) => (
<button
className={`dark:text-white first:rounded-t-md last:rounded-b-md space-x-2 group flex items-center w-full px-2 py-2 text-sm ${
active && 'bg-gray-200 dark:bg-gray-800'
}`}
>
{language.flag}
<p className="font-medium">{language.name}</p>
</button>
)}
</Menu.Item>
))}
{/* ... */}
</Menu.Items>
</div>
</Menu>
);
};
@@ -1,23 +1,23 @@
import React from 'react';
import { Button } from '@components/generic/Button';
import { openMobileNav } from '@core/slices/appSlice';
import { MenuIcon } from '@heroicons/react/outline';
import { openMobileNav } from '../../../core/slices/appSlice';
import { useAppDispatch } from '../../../hooks/redux';
import { IconButton } from '../../generic/IconButton';
export const MobileNavToggle = (): JSX.Element => {
const dispatch = useAppDispatch();
return (
<div className="md:hidden">
<IconButton
onClick={() => {
<Button
icon={<MenuIcon className="w-5 h-5" />}
onClick={(): void => {
dispatch(openMobileNav());
}}
>
<MenuIcon className="h-6 w-6" />
</IconButton>
circle
/>
</div>
);
};
+14 -13
View File
@@ -1,26 +1,27 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from '@app/hooks/redux';
import { Button } from '@components/generic/Button';
import { setDarkModeEnabled } from '@core/slices/appSlice';
import { MoonIcon, SunIcon } from '@heroicons/react/outline';
import { setDarkModeEnabled } from '../../../core/slices/appSlice';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
import { IconButton } from '../../generic/IconButton';
export const ThemeToggle = (): JSX.Element => {
const dispatch = useAppDispatch();
const darkMode = useAppSelector((state) => state.app.darkMode);
return (
<IconButton
onClick={() => {
<Button
icon={
darkMode ? (
<SunIcon className="w-5 h-5" />
) : (
<MoonIcon className="w-5 h-5" />
)
}
circle
onClick={(): void => {
dispatch(setDarkModeEnabled(!darkMode));
}}
>
{darkMode ? (
<SunIcon className="h-6 w-6" />
) : (
<MoonIcon className="h-6 w-6" />
)}
</IconButton>
/>
);
};
-44
View File
@@ -1,44 +0,0 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { XCircleIcon } from '@heroicons/react/outline';
import type { Protobuf } from '@meshtastic/meshtasticjs';
import { connection } from '../../core/connection';
export interface NodeProps {
node: Protobuf.NodeInfo;
onClose: () => void;
}
export const NodeDetails = ({ node }: NodeProps): JSX.Element => {
const { register, handleSubmit } = useForm<Protobuf.User>({
defaultValues: node.user,
});
const onSubmit = handleSubmit((data) => {
console.log(data);
connection.setOwner(data);
});
return (
<div>
<div className="flex dark:bg-primaryDark p-2 rounded-t-md justify-between border-b dark:border-gray-600 dark:text-white">
<div>{node.user?.longName ?? node.num}</div>
<XCircleIcon className="h-5 w-5 dark:text-white my-auto" />
</div>
<div>
<form onSubmit={onSubmit}>
{/* <Input label="Node Name" {...register('longName', {})} /> */}
<button
type="submit"
className="w-full rounded-md dark:bg-primaryDark shadow-md border dark:border-gray-600 p-2 mt-6 dark:text-white hover:bg-gray-200 dark:hover:bg-gray-900"
>
Save
</button>
</form>
</div>
</div>
);
};
+15 -3
View File
@@ -4,16 +4,21 @@ export interface PrimaryTemplateProps {
children: React.ReactNode;
title: string;
tagline: string;
button?: JSX.Element;
footer?: JSX.Element;
}
export const PrimaryTemplate = ({
children,
title,
tagline,
button,
footer,
}: PrimaryTemplateProps): JSX.Element => {
return (
<div className="flex flex-col flex-auto min-w-0">
<div className="flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between p-6 sm:py-8 sm:px-10 border-b dark:border-gray-600 bg-white dark:bg-secondaryDark">
<div className="flex p-6 bg-white border-b md:flex-row flex-0 md:items-center md:justify-between md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
{button && <div className="pr-2 m-auto md:hidden">{button}</div>}
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center font-medium">
<div>
@@ -21,13 +26,20 @@ export const PrimaryTemplate = ({
</div>
</div>
<div className="mt-2">
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight leading-7 sm:leading-10 truncate dark:text-white">
<h2 className="text-3xl font-extrabold leading-7 tracking-tight truncate md:text-4xl md:leading-10 dark:text-white">
{title}
</h2>
</div>
</div>
</div>
<div className="flex-auto p-6 sm:p-10 ">{children}</div>
<div className="flex-auto flex-grow p-6 md:p-10">{children}</div>
{footer && (
<div className="flex p-6 bg-white border-t md:flex-row flex-0 md:items-center md:justify-between md:py-8 md:px-10 dark:border-gray-600 dark:bg-secondaryDark">
{button && <div className="pr-2 m-auto md:hidden">{button}</div>}
<div className="flex-1 min-w-0">{footer}</div>
</div>
)}
</div>
);
};