mirror of
https://github.com/meshtastic/meshtastic.git
synced 2024-11-09 23:24:10 -08:00
Hardwar page work
This commit is contained in:
parent
ebaa8951ff
commit
f89b477cbb
|
@ -15,11 +15,13 @@
|
|||
"dependencies": {
|
||||
"@algolia/client-search": "^4.13.0",
|
||||
"@docusaurus/core": "^2.0.0-beta.18",
|
||||
"@docusaurus/plugin-content-docs": "^2.0.0-beta.18",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.18",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"dotenv": "^16.0.0",
|
||||
"framer-motion": "^6.2.8",
|
||||
"postcss": "^8.4.12",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"swr": "^1.2.2",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"use-breakpoint": "^3.0.2",
|
||||
"victory": "^36.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -4,6 +4,7 @@ specifiers:
|
|||
'@algolia/client-search': ^4.13.0
|
||||
'@docusaurus/core': ^2.0.0-beta.18
|
||||
'@docusaurus/module-type-aliases': ^2.0.0-beta.18
|
||||
'@docusaurus/plugin-content-docs': ^2.0.0-beta.18
|
||||
'@docusaurus/preset-classic': ^2.0.0-beta.18
|
||||
'@headlessui/react': ^1.5.0
|
||||
'@mdx-js/react': ^1.6.22
|
||||
|
@ -16,6 +17,7 @@ specifiers:
|
|||
'@types/w3c-web-serial': ^1.0.2
|
||||
autoprefixer: ^10.4.4
|
||||
dotenv: ^16.0.0
|
||||
framer-motion: ^6.2.8
|
||||
postcss: ^8.4.12
|
||||
prettier: ^2.6.1
|
||||
react: ^17.0.2
|
||||
|
@ -27,16 +29,19 @@ specifiers:
|
|||
tailwindcss: ^3.0.23
|
||||
typescript: ^4.6.3
|
||||
url-search-params-polyfill: ^8.1.1
|
||||
use-breakpoint: ^3.0.2
|
||||
victory: ^36.3.1
|
||||
|
||||
dependencies:
|
||||
'@algolia/client-search': 4.13.0
|
||||
'@docusaurus/core': 2.0.0-beta.18_28e7016540b0a32b5f8d7be755522ab7
|
||||
'@docusaurus/plugin-content-docs': 2.0.0-beta.18_28e7016540b0a32b5f8d7be755522ab7
|
||||
'@docusaurus/preset-classic': 2.0.0-beta.18_cd2ecee38e568e94b4890eebc445da58
|
||||
'@headlessui/react': 1.5.0_react-dom@17.0.2+react@17.0.2
|
||||
'@mdx-js/react': 1.6.22_react@17.0.2
|
||||
autoprefixer: 10.4.4_postcss@8.4.12
|
||||
dotenv: 16.0.0
|
||||
framer-motion: 6.2.8_react-dom@17.0.2+react@17.0.2
|
||||
postcss: 8.4.12
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
|
@ -46,6 +51,7 @@ dependencies:
|
|||
swr: 1.2.2_react@17.0.2
|
||||
tailwindcss: 3.0.23_autoprefixer@10.4.4
|
||||
url-search-params-polyfill: 8.1.1
|
||||
use-breakpoint: 3.0.2_react-dom@17.0.2+react@17.0.2
|
||||
victory: 36.3.1_react@17.0.2
|
||||
|
||||
devDependencies:
|
||||
|
@ -638,7 +644,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.12.9
|
||||
'@babel/helper-plugin-utils': 7.10.4
|
||||
'@babel/helper-plugin-utils': 7.16.7
|
||||
'@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.12.9
|
||||
'@babel/plugin-transform-parameters': 7.16.7_@babel+core@7.12.9
|
||||
dev: false
|
||||
|
@ -2182,6 +2188,19 @@ packages:
|
|||
- webpack-cli
|
||||
dev: false
|
||||
|
||||
/@emotion/is-prop-valid/0.8.8:
|
||||
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.7.4
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/memoize/0.7.4:
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@eslint/eslintrc/1.2.1:
|
||||
resolution: {integrity: sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
@ -5182,6 +5201,29 @@ packages:
|
|||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
dev: false
|
||||
|
||||
/framer-motion/6.2.8_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-4PtBWFJ6NqR350zYVt9AsFDtISTqsdqna79FvSYPfYDXuuqFmiKtZdkTnYPslnsOMedTW0pEvaQ7eqjD+sA+HA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||
react-dom: '>=16.8 || ^17.0.0 || ^18.0.0'
|
||||
dependencies:
|
||||
framesync: 6.0.1
|
||||
hey-listen: 1.0.8
|
||||
popmotion: 11.0.3
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
style-value-types: 5.0.0
|
||||
tslib: 2.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 0.8.8
|
||||
dev: false
|
||||
|
||||
/framesync/6.0.1:
|
||||
resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -5519,6 +5561,10 @@ packages:
|
|||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/hey-listen/1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
dev: false
|
||||
|
||||
/history/4.10.1:
|
||||
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
|
||||
dependencies:
|
||||
|
@ -6976,6 +7022,15 @@ packages:
|
|||
find-up: 3.0.0
|
||||
dev: false
|
||||
|
||||
/popmotion/11.0.3:
|
||||
resolution: {integrity: sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==}
|
||||
dependencies:
|
||||
framesync: 6.0.1
|
||||
hey-listen: 1.0.8
|
||||
style-value-types: 5.0.0
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
|
||||
/portfinder/1.0.28:
|
||||
resolution: {integrity: sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==}
|
||||
engines: {node: '>= 0.12.0'}
|
||||
|
@ -8567,6 +8622,13 @@ packages:
|
|||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
|
||||
/style-value-types/5.0.0:
|
||||
resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==}
|
||||
dependencies:
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
|
||||
/stylehacks/5.1.0_postcss@8.4.12:
|
||||
resolution: {integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==}
|
||||
engines: {node: ^10 || ^12 || >=14.0}
|
||||
|
@ -9005,6 +9067,17 @@ packages:
|
|||
resolution: {integrity: sha512-KmkCs6SjE6t4ihrfW9JelAPQIIIFbJweaaSLTh/4AO+c58JlDcb+GbdPt8yr5lRcFg4rPswRFRRhBGpWwh0K/Q==}
|
||||
dev: false
|
||||
|
||||
/use-breakpoint/3.0.2_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-O7qfp6QOOQI3CqLnJg6e5TgV2lSuLv3jQT48RAtScYTPbYjuGogPDQUS+Bz/MC0ZCYdZge262rKBi+jgZgCSHw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
dependencies:
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
dev: false
|
||||
|
||||
/use-composed-ref/1.2.1_react@17.0.2:
|
||||
resolution: {integrity: sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==}
|
||||
peerDependencies:
|
||||
|
|
16
src/components/Button.tsx
Normal file
16
src/components/Button.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
|
||||
import { HTMLMotionProps, motion } from 'framer-motion';
|
||||
|
||||
export const Button = ({ children, ...props }: HTMLMotionProps<'div'>) => {
|
||||
return (
|
||||
<motion.div
|
||||
{...props}
|
||||
whileHover={{ scale: 1.1, backgroundColor: 'var(--tertiary)' }}
|
||||
whileTap={{ scale: 1.0 }}
|
||||
className="m-auto flex cursor-pointer rounded-full bg-secondary p-3 shadow-md"
|
||||
>
|
||||
<div className="m-auto">{children}</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import { Dialog } from '@headlessui/react';
|
||||
|
||||
export interface ModalProps {
|
||||
open: boolean;
|
||||
|
@ -10,45 +12,38 @@ export interface ModalProps {
|
|||
|
||||
export const Modal = ({ open, onClose, children }: ModalProps): JSX.Element => {
|
||||
return (
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<AnimatePresence initial={false} exitBeforeEnter={true}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="fixed inset-0 z-10 overflow-y-auto"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="min-h-screen px-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<Dialog.Overlay className="fixed inset-0 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
</motion.div>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span
|
||||
className="inline-block h-screen align-middle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-100"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
{children}
|
||||
</Transition.Child>
|
||||
<div className="inline-block w-full transform text-left align-middle transition-all 2xl:max-w-7xl">
|
||||
<div className="group relative">
|
||||
<div className="animate-tilt absolute -inset-0.5 rotate-2 rounded-lg bg-accent shadow-md transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
|
||||
<div className="relative flex aspect-[3/2] flex-col overflow-hidden rounded-2xl bg-base shadow-md md:aspect-[2/1] md:flex-row md:bg-primary">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,67 +1,58 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { IDevice, Stability } from '@site/src/data/device';
|
||||
|
||||
import { HardwareModal } from './HardwareModal';
|
||||
|
||||
export interface HardwareCardProps {
|
||||
device: IDevice;
|
||||
setDevice: () => void;
|
||||
}
|
||||
|
||||
export const HardwareCard = ({ device }: HardwareCardProps): JSX.Element => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
export const HardwareCard = ({
|
||||
device,
|
||||
setDevice,
|
||||
}: HardwareCardProps): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<li
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<div
|
||||
className={`flex aspect-[4/3] overflow-hidden ${device.misc.Gradient}`}
|
||||
>
|
||||
<img
|
||||
src={device.misc.ImagePath}
|
||||
alt=""
|
||||
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2 group-hover:opacity-75"
|
||||
<li
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
setDevice();
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<div
|
||||
className={`flex aspect-[4/3] overflow-hidden ${device.misc.Gradient}`}
|
||||
>
|
||||
<img
|
||||
src={device.images.Front}
|
||||
alt=""
|
||||
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2 group-hover:opacity-75"
|
||||
/>
|
||||
</div>
|
||||
<button type="button" className="absolute inset-0 focus:outline-none">
|
||||
<span className="sr-only">View details for {device.name}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
|
||||
{device.name}
|
||||
</p>
|
||||
<p className="pointer-events-none flex gap-1 text-sm font-medium text-mute">
|
||||
<div
|
||||
className={`my-auto h-3 w-3 rounded-full ${
|
||||
device.misc.Stability === Stability.Broken
|
||||
? 'bg-red-500'
|
||||
: device.misc.Stability === Stability.Unstable
|
||||
? 'bg-orange-500'
|
||||
: device.misc.Stability === Stability.Semi
|
||||
? 'bg-cyan-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<button type="button" className="absolute inset-0 focus:outline-none">
|
||||
<span className="sr-only">View details for {device.name}</span>
|
||||
</button>
|
||||
<div className="my-auto">{Stability[device.misc.Stability]}</div>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
|
||||
{device.name}
|
||||
</p>
|
||||
<p className="pointer-events-none flex gap-1 text-sm font-medium text-mute">
|
||||
<div
|
||||
className={`my-auto h-3 w-3 rounded-full ${
|
||||
device.misc.Stability === Stability.Broken
|
||||
? 'bg-red-500'
|
||||
: device.misc.Stability === Stability.Unstable
|
||||
? 'bg-orange-500'
|
||||
: device.misc.Stability === Stability.Semi
|
||||
? 'bg-cyan-500'
|
||||
: 'bg-green-500'
|
||||
}`}
|
||||
/>
|
||||
<div className="my-auto">{Stability[device.misc.Stability]}</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<HardwareModal
|
||||
open={open}
|
||||
close={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
device={device}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { FiBluetooth, FiChevronRight, FiWifi, FiX } from 'react-icons/fi';
|
||||
import { useBreakpoint } from 'use-breakpoint';
|
||||
|
||||
import { Tab, Transition } from '@headlessui/react';
|
||||
import { Tab } from '@headlessui/react';
|
||||
import type { IDevice } from '@site/src/data/device';
|
||||
|
||||
import { Button } from '../../components/Button';
|
||||
import { BREAKPOINTS } from '../../utils/breakpoints';
|
||||
import { Modal } from '../Modal';
|
||||
import { Badge } from './Badge';
|
||||
import { CardTab } from './CardTab';
|
||||
|
@ -24,102 +28,120 @@ export const HardwareModal = ({
|
|||
open,
|
||||
close,
|
||||
}: HardwareModal): JSX.Element => {
|
||||
const colors = ['#428517', '#77D200', '#D6D305', '#EC8E19', '#C92B05'];
|
||||
const [hideDetails, setHideDetails] = useState(false);
|
||||
const { breakpoint } = useBreakpoint(BREAKPOINTS);
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={close}>
|
||||
<div className="inline-block w-full max-w-md transform overflow-hidden rounded-2xl bg-base text-left align-middle transition-all md:max-w-2xl md:bg-primary lg:max-w-4xl xl:max-w-6xl">
|
||||
<div className="flex aspect-[3/2] flex-col md:aspect-[2/1] md:flex-row">
|
||||
<div
|
||||
className={`relative flex h-full rounded-t-2xl md:rounded-l-2xl md:rounded-tr-none ${
|
||||
device.misc.Gradient
|
||||
} ${hideDetails ? 'w-full' : ''}`}
|
||||
>
|
||||
<img
|
||||
src={device.misc.ImagePath}
|
||||
alt=""
|
||||
className="pointer-events-none m-auto object-cover p-2 group-hover:opacity-75"
|
||||
/>
|
||||
<div className="absolute -bottom-4 flex w-full md:bottom-auto md:-right-4 md:h-full md:w-auto ">
|
||||
<div
|
||||
onClick={() => {
|
||||
setHideDetails(!hideDetails);
|
||||
}}
|
||||
className="m-auto flex cursor-pointer rounded-full bg-secondary p-2 shadow-md hover:bg-tertiary"
|
||||
>
|
||||
<FiChevronRight
|
||||
className={`m-auto ${
|
||||
hideDetails
|
||||
? 'rotate-90 md:rotate-180'
|
||||
: '-rotate-90 md:rotate-0'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!hideDetails && (
|
||||
<div className="absolute -bottom-3 right-0 m-auto mr-2 ml-auto flex gap-2 md:bottom-2 md:mr-14 md:mt-2">
|
||||
{device.features.BLE && (
|
||||
<Badge
|
||||
name="Bluetooth"
|
||||
color="bg-blue-500"
|
||||
icon={<FiBluetooth />}
|
||||
/>
|
||||
)}
|
||||
{device.features.WiFi && (
|
||||
<Badge name="WiFi" color="bg-orange-500" icon={<FiWifi />} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute right-0 mr-2 flex cursor-pointer rounded-b-full bg-secondary p-3 shadow-md hover:bg-tertiary md:mt-2 md:rounded-full"
|
||||
onClick={close}
|
||||
>
|
||||
<FiX className="m-auto" />
|
||||
</div>
|
||||
<div
|
||||
className={`transition-[all] duration-100 ease-linear ${
|
||||
hideDetails ? 'h-7 bg-base md:h-auto md:w-7' : 'w-full'
|
||||
}`}
|
||||
>
|
||||
<Transition
|
||||
appear
|
||||
as={'div'}
|
||||
className="flex h-full flex-col"
|
||||
show={!hideDetails}
|
||||
enter="ease-out duration-100 delay-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-100 delay-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
<div className="absolute right-0 z-20 m-2 md:flex">
|
||||
<Button onClick={close}>
|
||||
<FiX />
|
||||
</Button>
|
||||
</div>
|
||||
<motion.div
|
||||
layout
|
||||
animate={hideDetails ? 'hidden' : 'visible'}
|
||||
variants={{
|
||||
hidden: breakpoint === 'sm' ? { height: '100%' } : { width: '100%' },
|
||||
visible: breakpoint === 'sm' ? { height: '25%' } : { width: '20%' },
|
||||
}}
|
||||
transition={{
|
||||
type: 'just',
|
||||
}}
|
||||
className="absolute inset-0 flex flex-col md:h-full md:flex-row"
|
||||
>
|
||||
<motion.div
|
||||
layout
|
||||
className={`relative z-10 flex h-full w-full rounded-t-2xl md:rounded-l-2xl md:rounded-tr-none ${device.misc.Gradient}`}
|
||||
>
|
||||
<motion.img
|
||||
layout
|
||||
src={device.images.Front}
|
||||
alt=""
|
||||
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2"
|
||||
/>
|
||||
<div className="absolute -bottom-5 z-20 flex w-full md:bottom-auto md:-right-5 md:h-full md:w-auto">
|
||||
<Button
|
||||
animate={hideDetails ? 'hidden' : 'visible'}
|
||||
variants={{
|
||||
hidden: breakpoint === 'sm' ? { rotate: -90 } : { rotate: 180 },
|
||||
visible: breakpoint === 'sm' ? { rotate: 90 } : { rotate: 0 },
|
||||
}}
|
||||
onClick={() => {
|
||||
setHideDetails(!hideDetails);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div className="flex shadow-md md:pb-2">
|
||||
<VariantSelectButton options={device.variants} />
|
||||
</div>
|
||||
<div className="flex h-full bg-base p-2 md:p-4">
|
||||
<Tab.Group
|
||||
as="div"
|
||||
className="flex-grow rounded-2xl bg-primary p-2"
|
||||
>
|
||||
<Tab.List className="flex gap-2">
|
||||
<CardTab title="Info" />
|
||||
<CardTab title="Power" />
|
||||
<CardTab title="Pinout" />
|
||||
</Tab.List>
|
||||
<Tab.Panels as="div" className="">
|
||||
<InfoTab device={device} />
|
||||
<PowerTab device={device} />
|
||||
<PinoutTab device={device} />
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</>
|
||||
</Transition>
|
||||
<FiChevronRight />
|
||||
</Button>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{!hideDetails && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className={`absolute -bottom-5 z-20 flex md:mt-0 md:hidden md:pb-2 ${
|
||||
hideDetails ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<VariantSelectButton options={device.variants} />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute -bottom-3 right-0 m-auto mr-2 ml-auto flex gap-2 md:bottom-2 md:mr-14 md:mt-2"
|
||||
>
|
||||
{device.features.BLE && (
|
||||
<Badge
|
||||
name="Bluetooth"
|
||||
color="bg-blue-500"
|
||||
icon={<FiBluetooth />}
|
||||
/>
|
||||
)}
|
||||
{device.features.WiFi && (
|
||||
<Badge
|
||||
name="WiFi"
|
||||
color="bg-orange-500"
|
||||
icon={<FiWifi />}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
<div
|
||||
className={`h-7 bg-base opacity-0 md:h-auto md:w-7 ${
|
||||
hideDetails ? 'flex' : 'hidden'
|
||||
}`}
|
||||
/>
|
||||
</motion.div>
|
||||
<div className="mt-[25%] flex h-full flex-col md:ml-[20%] md:mt-0 md:w-4/5">
|
||||
<div className="z-0 hidden pb-2 md:flex">
|
||||
<VariantSelectButton options={device.variants} />
|
||||
</div>
|
||||
<div
|
||||
className={`mt-1 flex flex-grow rounded-2xl bg-base p-2 shadow-inner transition-opacity duration-100 ease-linear md:mt-0 md:rounded-l-none md:rounded-r-2xl md:p-4 ${
|
||||
hideDetails ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<Tab.Group
|
||||
as="div"
|
||||
className="flex flex-grow flex-col rounded-2xl bg-primary p-2"
|
||||
>
|
||||
<Tab.List className="flex gap-2">
|
||||
<CardTab title="Info" />
|
||||
<CardTab title="Power" />
|
||||
<CardTab title="Pinout" />
|
||||
</Tab.List>
|
||||
<Tab.Panels as="div" className="flex-grow overflow-y-auto">
|
||||
<InfoTab device={device} />
|
||||
<PowerTab device={device} />
|
||||
<PinoutTab device={device} />
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -9,5 +9,40 @@ export interface InfoTabProps {
|
|||
}
|
||||
|
||||
export const InfoTab = ({ device }: InfoTabProps): JSX.Element => {
|
||||
return <Tab.Panel className="h-32">Content 1</Tab.Panel>;
|
||||
return (
|
||||
<Tab.Panel>
|
||||
<div className="px-4 py-5 sm:p-0">
|
||||
<dl className="sm:divide-y sm:divide-gray-200">
|
||||
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
|
||||
<dt className="text-sm font-medium text-secondaryInv">
|
||||
BLE/WiFi Version
|
||||
</dt>
|
||||
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
|
||||
<span className="rounded-md bg-secondary px-0.5">
|
||||
{device.specifications.BLEVersion}
|
||||
</span>
|
||||
/
|
||||
<span className="rounded-md bg-secondary px-0.5">
|
||||
{device.specifications.WiFiVersion}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
|
||||
<dt className="text-sm font-medium text-secondaryInv">
|
||||
BLE/WiFi Antenna
|
||||
</dt>
|
||||
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
|
||||
<span className="rounded-md bg-secondary px-0.5">
|
||||
{device.specifications.BLEAntenna}
|
||||
</span>
|
||||
/
|
||||
<span className="rounded-md bg-secondary px-0.5">
|
||||
{device.specifications.WiFiAntenna}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Fragment, useState } from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { FiCheck } from 'react-icons/fi';
|
||||
import { HiSelector } from 'react-icons/hi';
|
||||
|
||||
|
@ -19,15 +20,21 @@ export const VariantSelectButton = ({
|
|||
<Listbox value={selected} onChange={setSelected}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="relative">
|
||||
<Listbox.Button className="relative -mt-5 ml-2 flex w-fit gap-1 rounded-lg bg-secondary p-2 py-2 pl-3 pr-10 text-lg font-medium leading-6 shadow-md hover:bg-tertiary md:mt-2">
|
||||
<span className="block truncate">{selected.name}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<HiSelector
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<div className="relative select-none">
|
||||
<Listbox.Button as={Fragment}>
|
||||
<motion.button
|
||||
whileHover={{ backgroundColor: 'var(--tertiary)' }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
className="relative -mt-5 ml-2 flex w-fit gap-1 rounded-lg bg-secondary p-2 py-2 pl-3 pr-10 text-lg font-medium leading-6 shadow-md md:mt-2"
|
||||
>
|
||||
<span className="block truncate">{selected.name}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<HiSelector
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</motion.button>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
|
@ -37,7 +44,7 @@ export const VariantSelectButton = ({
|
|||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-primary py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-primary py-1 shadow-md ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{options.map((variant, index) => (
|
||||
<Listbox.Option
|
||||
key={index}
|
||||
|
|
|
@ -19,8 +19,9 @@ export const SocialCard = ({
|
|||
>
|
||||
{children}
|
||||
<a
|
||||
className="absolute top-0 left-0 right-0 bottom-0 hidden rounded-xl border border-accent bg-secondaryDark bg-opacity-95 text-2xl shadow-xl group-hover:flex"
|
||||
className="absolute top-0 left-0 right-0 bottom-0 hidden rounded-xl border border-accent bg-secondary bg-opacity-95 text-2xl shadow-xl group-hover:flex"
|
||||
href={link}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FiExternalLink className="m-auto" />
|
||||
|
|
|
@ -48,9 +48,12 @@ export interface IDevice {
|
|||
misc: {
|
||||
SuggestedUse: UseCase[];
|
||||
Stability: Stability;
|
||||
ImagePath: string;
|
||||
Gradient: string;
|
||||
};
|
||||
images: {
|
||||
Front: string;
|
||||
Back: string;
|
||||
};
|
||||
features: {
|
||||
BLE: boolean;
|
||||
WiFi: boolean;
|
||||
|
|
|
@ -5,8 +5,11 @@ export const heltec: IDevice = {
|
|||
misc: {
|
||||
Stability: Stability.Unstable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
ImagePath: '/img/hardware/heltec-v2.png',
|
||||
Gradient: 'from-pink-300 via-purple-300 to-indigo-400',
|
||||
Gradient: 'bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/heltec-v2.png',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
|
|
|
@ -5,9 +5,12 @@ export const hydra: IDevice = {
|
|||
misc: {
|
||||
Stability: Stability.Stable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
ImagePath: '/img/hardware/Hydra-PCB.2.1.svg',
|
||||
Gradient: 'bg-gradient-to-r from-indigo-200 via-red-200 to-yellow-100',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/Hydra-PCB.2.1.svg',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
WiFi: true,
|
||||
|
|
66
src/data/devices/nano_g1.ts
Normal file
66
src/data/devices/nano_g1.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { IDevice, Stability, UseCase } from '../device';
|
||||
|
||||
export const nano_g1: IDevice = {
|
||||
name: 'Nano G1',
|
||||
misc: {
|
||||
Stability: Stability.Unstable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
Gradient: 'bg-gradient-to-r from-green-200 to-green-500',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/nano_g1_front.svg',
|
||||
Back: '/img/hardware/nano_g1_back.svg',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
WiFi: true,
|
||||
Modules: [
|
||||
'cannedMessage',
|
||||
'externalNotification',
|
||||
'rangeTest',
|
||||
'rotaryEncoder',
|
||||
'storeAndForward',
|
||||
'telemetry',
|
||||
],
|
||||
},
|
||||
specifications: {
|
||||
BLEVersion: '4.2',
|
||||
BLEAntenna: 'Integrated',
|
||||
WiFiVersion: '2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS',
|
||||
WiFiAntenna: 'Integrated',
|
||||
Chipset: 'ESP32',
|
||||
Driver: 'CH9102',
|
||||
GNSS: 'NEO-6M',
|
||||
FlashSize: 4,
|
||||
Frequencies: [433, 868, 915, 923],
|
||||
LoRa: 'SX1262',
|
||||
PSRAM: 8,
|
||||
RAM: undefined,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
name: 'TBeam 0.7',
|
||||
misc: {
|
||||
Stability: Stability.Unstable,
|
||||
},
|
||||
specifications: {
|
||||
Driver: 'CP210X',
|
||||
GNSS: 'NEO-6M',
|
||||
Frequencies: [868, 915],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TBeam 1.0',
|
||||
specifications: {
|
||||
Frequencies: [868, 915],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TBeam 1.1',
|
||||
specifications: {
|
||||
Driver: 'CP210X',
|
||||
GNSS: 'NEO-6M',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
66
src/data/devices/rak19001.ts
Normal file
66
src/data/devices/rak19001.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { IDevice, Stability, UseCase } from '../device';
|
||||
|
||||
export const rak19001: IDevice = {
|
||||
name: 'WisBlock 19001',
|
||||
misc: {
|
||||
Stability: Stability.Stable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
Gradient: 'bg-gradient-to-r from-indigo-300 to-purple-400',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/rak/RAK19001.png',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
WiFi: true,
|
||||
Modules: [
|
||||
'cannedMessage',
|
||||
'externalNotification',
|
||||
'rangeTest',
|
||||
'rotaryEncoder',
|
||||
'storeAndForward',
|
||||
'telemetry',
|
||||
],
|
||||
},
|
||||
specifications: {
|
||||
BLEVersion: '4.2',
|
||||
BLEAntenna: 'Integrated',
|
||||
WiFiVersion: '2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS',
|
||||
WiFiAntenna: 'Integrated',
|
||||
Chipset: 'ESP32',
|
||||
Driver: 'CH9102',
|
||||
GNSS: 'NEO-6M',
|
||||
FlashSize: 4,
|
||||
Frequencies: [433, 868, 915, 923],
|
||||
LoRa: 'SX1262',
|
||||
PSRAM: 8,
|
||||
RAM: undefined,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
name: 'TBeam 0.7',
|
||||
misc: {
|
||||
Stability: Stability.Unstable,
|
||||
},
|
||||
specifications: {
|
||||
Driver: 'CP210X',
|
||||
GNSS: 'NEO-6M',
|
||||
Frequencies: [868, 915],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TBeam 1.0',
|
||||
specifications: {
|
||||
Frequencies: [868, 915],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TBeam 1.1',
|
||||
specifications: {
|
||||
Driver: 'CP210X',
|
||||
GNSS: 'NEO-6M',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -5,9 +5,12 @@ export const rak19003: IDevice = {
|
|||
misc: {
|
||||
Stability: Stability.Stable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
ImagePath: '/img/hardware/rak/RAK19003.png',
|
||||
Gradient: 'bg-gradient-to-b from-orange-500 to-yellow-300',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/rak/RAK19003.png',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
WiFi: true,
|
||||
|
|
|
@ -5,9 +5,12 @@ export const tbeam: IDevice = {
|
|||
misc: {
|
||||
Stability: Stability.Stable,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
ImagePath: '/img/hardware/tbeam-v1.1.svg',
|
||||
Gradient: 'bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/tbeam-v1.1.svg',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
WiFi: true,
|
||||
|
|
|
@ -5,8 +5,11 @@ export const techo: IDevice = {
|
|||
misc: {
|
||||
Stability: Stability.Semi,
|
||||
SuggestedUse: [UseCase.Portable],
|
||||
ImagePath: '/img/hardware/t-echo-lilygo.jpg',
|
||||
Gradient: 'from-gray-700 via-gray-900 to-black',
|
||||
Gradient: 'bg-gradient-to-r from-gray-700 via-gray-900 to-black',
|
||||
},
|
||||
images: {
|
||||
Front: '/img/hardware/t-echo-lilygo.jpg',
|
||||
Back: '',
|
||||
},
|
||||
features: {
|
||||
BLE: true,
|
||||
|
|
|
@ -1,28 +1,23 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { FiPlus } from 'react-icons/fi';
|
||||
|
||||
import { HardwareModal } from '@site/src/components/hardware/HardwareModal';
|
||||
import { IDevice } from '@site/src/data/device';
|
||||
|
||||
import { HardwareCard } from '../../components/hardware/HardwareCard';
|
||||
import { PageLayout } from '../../components/PageLayout';
|
||||
import { heltec } from '../../data/devices/heltec';
|
||||
import { hydra } from '../../data/devices/hydra';
|
||||
import { nano_g1 } from '../../data/devices/nano_g1';
|
||||
import { rak19001 } from '../../data/devices/rak19001';
|
||||
import { rak19003 } from '../../data/devices/rak19003';
|
||||
import { tbeam } from '../../data/devices/tbeam';
|
||||
import { techo } from '../../data/devices/techo';
|
||||
|
||||
const Hardware = (): JSX.Element => {
|
||||
const hardware = [
|
||||
tbeam,
|
||||
hydra,
|
||||
rak19003,
|
||||
heltec,
|
||||
techo,
|
||||
rak19003,
|
||||
rak19003,
|
||||
rak19003,
|
||||
rak19003,
|
||||
rak19003,
|
||||
];
|
||||
const hardware = [tbeam, hydra, rak19003, rak19001, nano_g1, heltec, techo];
|
||||
const [modalData, setModalData] = useState<IDevice>();
|
||||
|
||||
return (
|
||||
<PageLayout title="Hardware" description="Supported hardware">
|
||||
|
@ -57,7 +52,13 @@ const Hardware = (): JSX.Element => {
|
|||
className="grid grid-cols-2 gap-x-2 gap-y-4 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:grid-cols-5 xl:gap-x-4"
|
||||
>
|
||||
{hardware.map((device, index) => (
|
||||
<HardwareCard key={index} device={device} />
|
||||
<HardwareCard
|
||||
key={index}
|
||||
device={device}
|
||||
setDevice={(): void => {
|
||||
setModalData(device);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<li className="group relative">
|
||||
<a
|
||||
|
@ -77,6 +78,15 @@ const Hardware = (): JSX.Element => {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{modalData && (
|
||||
<HardwareModal
|
||||
open={!!modalData}
|
||||
close={() => {
|
||||
setModalData(undefined);
|
||||
}}
|
||||
device={modalData}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
|
1
src/utils/breakpoints.ts
Normal file
1
src/utils/breakpoints.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
|
77
static/img/hardware/nano_g1_back.svg
Normal file
77
static/img/hardware/nano_g1_back.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
77
static/img/hardware/nano_g1_front.svg
Normal file
77
static/img/hardware/nano_g1_front.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 473 KiB |
BIN
static/img/hardware/rak/RAK19001.png
Normal file
BIN
static/img/hardware/rak/RAK19001.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
BIN
static/img/hardware/rak/RAK19007.png
Normal file
BIN
static/img/hardware/rak/RAK19007.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
Loading…
Reference in a new issue