fix import paths, remove old form elements
This commit is contained in:
@@ -10,11 +10,11 @@ import {
|
||||
UsbIcon,
|
||||
NetworkIcon
|
||||
} from "lucide-react";
|
||||
import { Subtle } from "./UI/Typography/Subtle.js";
|
||||
import { H3 } from "./UI/Typography/H3.js";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.js";
|
||||
import { H3 } from "@components/UI/Typography/H3.js";
|
||||
import { useDeviceStore } from "@app/core/stores/deviceStore.js";
|
||||
import { useMemo } from "react";
|
||||
import { Separator } from "./UI/Seperator.js";
|
||||
import { Separator } from "@components/UI/Seperator.js";
|
||||
|
||||
export const Dashboard = () => {
|
||||
const { setConnectDialogOpen } = useAppStore();
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
GithubIcon,
|
||||
TerminalIcon
|
||||
} from "lucide-react";
|
||||
import { Separator } from "./UI/Seperator.js";
|
||||
import { Code } from "./UI/Typography/Code.js";
|
||||
import { Separator } from "@components/UI/Seperator.js";
|
||||
import { Code } from "@components/UI/Typography/Code.js";
|
||||
import { DeviceSelectorButton } from "./DeviceSelectorButton.js";
|
||||
|
||||
export const DeviceSelector = (): JSX.Element => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Button } from "@components/UI/Button.js";
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { Label } from "../UI/Label.js";
|
||||
import { Label } from "@components/UI/Label.js";
|
||||
|
||||
export interface User {
|
||||
longName: string;
|
||||
@@ -27,7 +27,7 @@ export const DeviceNameDialog = ({
|
||||
open,
|
||||
onOpenChange
|
||||
}: DeviceNameDialogProps): JSX.Element => {
|
||||
const { hardware, nodes, connection, setDialogOpen } = useDevice();
|
||||
const { hardware, nodes, connection } = useDevice();
|
||||
|
||||
const myNode = nodes.find((n) => n.data.num === hardware.myNodeNum);
|
||||
|
||||
@@ -45,7 +45,7 @@ export const DeviceNameDialog = ({
|
||||
...data
|
||||
})
|
||||
);
|
||||
setDialogOpen("deviceName", false);
|
||||
onOpenChange(false);
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toByteArray } from "base64-js";
|
||||
import { Checkbox } from "@components/form/Checkbox.js";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Checkbox } from "@components/UI/Checkbox.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
DialogTitle
|
||||
} from "@components/UI/Dialog.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { Toggle } from "@components/form/Toggle.js";
|
||||
import { Switch } from "@components/UI/Switch.js";
|
||||
import { Button } from "@components/UI/Button.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Label } from "@components/UI/Label.js";
|
||||
|
||||
export interface ImportDialogProps {
|
||||
open: boolean;
|
||||
@@ -82,8 +83,8 @@ export const ImportDialog = ({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Channel Set/QR Code URL</Label>
|
||||
<Input
|
||||
label="Channel Set/QR Code URL"
|
||||
value={QRCodeURL}
|
||||
suffix={validURL ? "✅" : "❌"}
|
||||
onChange={(e) => {
|
||||
@@ -94,8 +95,8 @@ export const ImportDialog = ({
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex w-full gap-2">
|
||||
<div className="w-36">
|
||||
<Toggle
|
||||
label="Use Preset?"
|
||||
<Label>Use Preset?</Label>
|
||||
<Switch
|
||||
disabled
|
||||
checked={channelSet?.loraConfig?.usePreset ?? true}
|
||||
/>
|
||||
@@ -121,14 +122,14 @@ export const ImportDialog = ({
|
||||
</span>
|
||||
<div className="flex w-40 flex-col gap-1">
|
||||
{channelSet?.settings.map((channel, index) => (
|
||||
<Checkbox
|
||||
key={index}
|
||||
label={
|
||||
channel.name.length
|
||||
<>
|
||||
<Label>
|
||||
{channel.name.length
|
||||
? channel.name
|
||||
: `Channel: ${channel.id}`
|
||||
}
|
||||
/>
|
||||
: `Channel: ${channel.id}`}
|
||||
</Label>
|
||||
<Checkbox key={index} />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -6,9 +5,14 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from "@components/UI/Dialog.js";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../UI/Tabs.js";
|
||||
import { Subtle } from "../UI/Typography/Subtle.js";
|
||||
import { Link } from "../UI/Typography/Link.js";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger
|
||||
} from "@components/UI/Tabs.js";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.js";
|
||||
import { Link } from "@components/UI/Typography/Link.js";
|
||||
import { HTTP } from "../PageComponents/Connect/HTTP.js";
|
||||
import { BLE } from "../PageComponents/Connect/BLE.js";
|
||||
import { Serial } from "../PageComponents/Connect/Serial.js";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { fromByteArray } from "base64-js";
|
||||
import { QRCode } from "react-qrcode-logo";
|
||||
import { Checkbox } from "@components/form/Checkbox.js";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Checkbox } from "@components/UI/Checkbox.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@components/UI/Dialog.js";
|
||||
import { ClipboardIcon } from "lucide-react";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { Label } from "@components/UI/Label.js";
|
||||
|
||||
export interface QRDialogProps {
|
||||
open: boolean;
|
||||
@@ -62,39 +63,44 @@ export const QRDialog = ({
|
||||
<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}
|
||||
label={
|
||||
channel.settings?.name.length
|
||||
<>
|
||||
<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]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
: `Channel: ${channel.index}`}
|
||||
</Label>
|
||||
<Checkbox
|
||||
key={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>
|
||||
<DialogFooter>
|
||||
<Label>Sharable URL</Label>
|
||||
<Input
|
||||
label="Sharable URL"
|
||||
value={QRCodeURL}
|
||||
disabled
|
||||
action={{
|
||||
icon: <ClipboardIcon size={16} />,
|
||||
action() {
|
||||
icon: ClipboardIcon,
|
||||
onClick() {
|
||||
void navigator.clipboard.writeText(QRCodeURL);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@components/UI/Dialog.js";
|
||||
import { ClockIcon, RefreshCwIcon } from "lucide-react";
|
||||
import { Button } from "@components/UI/Button.js";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
|
||||
export interface RebootDialogProps {
|
||||
open: boolean;
|
||||
@@ -21,7 +21,7 @@ export const RebootDialog = ({
|
||||
open,
|
||||
onOpenChange
|
||||
}: RebootDialogProps): JSX.Element => {
|
||||
const { connection, setDialogOpen } = useDevice();
|
||||
const { connection } = useDevice();
|
||||
|
||||
const [time, setTime] = useState<number>(5);
|
||||
|
||||
@@ -40,21 +40,19 @@ export const RebootDialog = ({
|
||||
value={time}
|
||||
onChange={(e) => setTime(parseInt(e.target.value))}
|
||||
action={{
|
||||
icon: <ClockIcon size={16} />,
|
||||
action() {
|
||||
connection
|
||||
?.reboot(time * 60)
|
||||
.then(() => setDialogOpen("reboot", false));
|
||||
icon: ClockIcon,
|
||||
onClick() {
|
||||
connection?.reboot(time * 60).then(() => onOpenChange(false));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="w-24"
|
||||
onClick={() => {
|
||||
connection?.reboot(2).then(() => setDialogOpen("reboot", false));
|
||||
connection?.reboot(2).then(() => onOpenChange(false));
|
||||
}}
|
||||
>
|
||||
<RefreshCwIcon size={16} />
|
||||
<RefreshCwIcon className="mr-2" size={16} />
|
||||
Now
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "@components/UI/Dialog.js";
|
||||
import { ClockIcon, PowerIcon } from "lucide-react";
|
||||
import { Button } from "@components/UI/Button.js";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
|
||||
export interface ShutdownDialogProps {
|
||||
open: boolean;
|
||||
@@ -20,7 +20,7 @@ export const ShutdownDialog = ({
|
||||
open,
|
||||
onOpenChange
|
||||
}: ShutdownDialogProps): JSX.Element => {
|
||||
const { connection, setDialogOpen } = useDevice();
|
||||
const { connection } = useDevice();
|
||||
|
||||
const [time, setTime] = useState<number>(5);
|
||||
|
||||
@@ -39,25 +39,24 @@ export const ShutdownDialog = ({
|
||||
type="number"
|
||||
value={time}
|
||||
onChange={(e) => setTime(parseInt(e.target.value))}
|
||||
action={{
|
||||
icon: <ClockIcon size={16} />,
|
||||
action() {
|
||||
connection
|
||||
?.shutdown(time * 60)
|
||||
.then(() => setDialogOpen("shutdown", false));
|
||||
}
|
||||
}}
|
||||
suffix="Minutes"
|
||||
/>
|
||||
<Button
|
||||
className="w-24"
|
||||
onClick={() => {
|
||||
connection
|
||||
?.shutdown(2)
|
||||
.then(() => setDialogOpen("shutdown", false));
|
||||
connection?.shutdown(time * 60).then(() => onOpenChange(false));
|
||||
}}
|
||||
>
|
||||
<PowerIcon size={16} />
|
||||
<span>Now</span>
|
||||
<ClockIcon size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
className="w-24"
|
||||
onClick={() => {
|
||||
connection?.shutdown(2).then(() => () => onOpenChange(false));
|
||||
}}
|
||||
>
|
||||
<PowerIcon className="mr-2" size={16} />
|
||||
Now
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
SubmitHandler,
|
||||
useForm
|
||||
} from "react-hook-form";
|
||||
import { Input } from "./UI/Input.js";
|
||||
import { Label } from "./UI/Label.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
import { Label } from "@components/UI/Label.js";
|
||||
import { ErrorMessage } from "@hookform/error-message";
|
||||
import {
|
||||
Select,
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "./UI/Select.js";
|
||||
import { Switch } from "./UI/Switch.js";
|
||||
import { H4 } from "./UI/Typography/H4.js";
|
||||
import { Subtle } from "./UI/Typography/Subtle.js";
|
||||
} from "@components/UI/Select.js";
|
||||
import { Switch } from "@components/UI/Switch.js";
|
||||
import { H4 } from "@components/UI/Typography/H4.js";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.js";
|
||||
|
||||
interface DisabledBy<T> {
|
||||
fieldName: Path<T>;
|
||||
@@ -114,6 +114,8 @@ export function DynamicForm<T extends FieldValues>({
|
||||
return (
|
||||
<FieldWrapper key={index} {...fieldWrapperData}>
|
||||
<Input
|
||||
type="text"
|
||||
suffix={field.suffix}
|
||||
disabled={fieldWrapperData.disabled}
|
||||
{...register(field.name)}
|
||||
/>
|
||||
@@ -129,8 +131,10 @@ export function DynamicForm<T extends FieldValues>({
|
||||
<Input
|
||||
type="number"
|
||||
value={parseInt(value)}
|
||||
suffix={field.suffix}
|
||||
onChange={(e) => onChange(parseInt(e.target.value))}
|
||||
disabled={fieldWrapperData.disabled}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -141,7 +145,13 @@ export function DynamicForm<T extends FieldValues>({
|
||||
<FieldWrapper key={index} {...fieldWrapperData}>
|
||||
<Input
|
||||
type="password"
|
||||
suffix={field.suffix}
|
||||
disabled={fieldWrapperData.disabled}
|
||||
// action={{
|
||||
// icon: hidden ? EyeIcon : EyeOffIcon,
|
||||
// onClick: () => {
|
||||
// }
|
||||
// }}
|
||||
{...register(field.name)}
|
||||
/>
|
||||
</FieldWrapper>
|
||||
|
||||
@@ -2,12 +2,13 @@ import { useEffect, useState } from "react";
|
||||
import { fromByteArray, toByteArray } from "base64-js";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ChannelSettingsValidation } from "@app/validation/channelSettings.js";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Toggle } from "@components/form/Toggle.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
import { Switch } from "@components/UI/Switch.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { RefreshCwIcon, EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { Label } from "@radix-ui/react-label";
|
||||
|
||||
export interface SettingsPanelProps {
|
||||
channel: Protobuf.Channel;
|
||||
@@ -94,18 +95,21 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
name="enabled"
|
||||
control={control}
|
||||
render={({ field: { value, ...rest } }) => (
|
||||
<Toggle
|
||||
label="Enabled"
|
||||
description="Description"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
<>
|
||||
<Label>Enabled</Label>
|
||||
<Switch
|
||||
// label="Enabled"
|
||||
// description="Description"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Label>Name</Label>
|
||||
<Input
|
||||
label="Name"
|
||||
description="Max transmit power in dBm"
|
||||
error={errors.name?.message}
|
||||
// description="Max transmit power in dBm"
|
||||
// error={errors.name?.message}
|
||||
{...register("name")}
|
||||
/>
|
||||
</>
|
||||
@@ -131,42 +135,49 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
<option value={128}>128 Bit</option>
|
||||
<option value={256}>256 Bit</option>
|
||||
</Select> */}
|
||||
<Label>Pre-Shared Key</Label>
|
||||
<Input
|
||||
width="100%"
|
||||
label="Pre-Shared Key"
|
||||
description="Channel key to encrypt data"
|
||||
// width="100%"
|
||||
// label="Pre-Shared Key"
|
||||
// description="Channel key to encrypt data"
|
||||
type={pskHidden ? "password" : "text"}
|
||||
action={{
|
||||
icon: pskHidden ? <EyeIcon size={16} /> : <EyeOffIcon size={16} />,
|
||||
action: () => {
|
||||
icon: pskHidden ? EyeIcon : EyeOffIcon,
|
||||
onClick: () => {
|
||||
setPskHidden(!pskHidden);
|
||||
}
|
||||
}}
|
||||
error={errors.psk?.message}
|
||||
// error={errors.psk?.message}
|
||||
{...register("psk")}
|
||||
/>
|
||||
<Controller
|
||||
name="uplinkEnabled"
|
||||
control={control}
|
||||
render={({ field: { value, ...rest } }) => (
|
||||
<Toggle
|
||||
label="Uplink Enabled"
|
||||
description="Send packets to designated MQTT server"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
<>
|
||||
<Label>Uplink Enabled</Label>
|
||||
<Switch
|
||||
// label="Uplink Enabled"
|
||||
// description="Send packets to designated MQTT server"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="downlinkEnabled"
|
||||
control={control}
|
||||
render={({ field: { value, ...rest } }) => (
|
||||
<Toggle
|
||||
label="Downlink Enabled"
|
||||
description="Recieve packets to designated MQTT server"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
<>
|
||||
<Label>Downlink Enabled</Label>
|
||||
<Switch
|
||||
// label="Downlink Enabled"
|
||||
// description="Recieve packets to designated MQTT server"
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BluetoothValidation } from "@app/validation/config/bluetooth.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Bluetooth = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DeviceValidation } from "@app/validation/config/device.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Device = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DisplayValidation } from "@app/validation/config/display.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Display = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
@@ -30,7 +30,8 @@ export const Display = (): JSX.Element => {
|
||||
type: "number",
|
||||
name: "screenOnSecs",
|
||||
label: "Screen Timeout",
|
||||
description: "Turn off the display after this long"
|
||||
description: "Turn off the display after this long",
|
||||
suffix: "seconds"
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { LoRaValidation } from "@app/validation/config/lora.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const LoRa = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NetworkValidation } from "@app/validation/config/network.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Network = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PositionValidation } from "@app/validation/config/position.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Position = (): JSX.Element => {
|
||||
const { config, nodes, hardware, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PowerValidation } from "@app/validation/config/power.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Power = (): JSX.Element => {
|
||||
const { config, setWorkingConfig } = useDevice();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Toggle } from "@components/form/Toggle.js";
|
||||
import { Input } from "@components/UI/Input.js";
|
||||
import { Switch } from "@components/UI/Switch.js";
|
||||
import { Button } from "@components/UI/Button.js";
|
||||
import { useAppStore } from "@core/stores/appStore.js";
|
||||
import { useDeviceStore } from "@core/stores/deviceStore.js";
|
||||
import { subscribeAll } from "@core/subscriptions.js";
|
||||
import { randId } from "@core/utils/randId.js";
|
||||
import { IHTTPConnection } from "@meshtastic/meshtasticjs";
|
||||
import { Label } from "@components/UI/Label.js";
|
||||
import { SelectLabel } from "@components/UI/Select.js";
|
||||
|
||||
export const HTTP = (): JSX.Element => {
|
||||
const { addDevice } = useDeviceStore();
|
||||
@@ -50,8 +52,9 @@ export const HTTP = (): JSX.Element => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
<form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}>
|
||||
<div className="flex h-48 flex-col gap-2">
|
||||
<Label>IP Address/Hostname</Label>
|
||||
<Input
|
||||
label="IP Address/Hostname"
|
||||
// label="IP Address/Hostname"
|
||||
prefix={TLSEnabled ? "https://" : "http://"}
|
||||
placeholder="000.000.000.000 / meshtastic.local"
|
||||
{...register("ip")}
|
||||
@@ -60,13 +63,16 @@ export const HTTP = (): JSX.Element => {
|
||||
name="tls"
|
||||
control={control}
|
||||
render={({ field: { value, ...rest } }) => (
|
||||
<Toggle
|
||||
label="Use TLS"
|
||||
description="Description"
|
||||
disabled={location.protocol === "https:"}
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
<>
|
||||
<Label>Use TLS</Label>
|
||||
<Switch
|
||||
// label="Use TLS"
|
||||
// description="Description"
|
||||
disabled={location.protocol === "https:"}
|
||||
checked={value}
|
||||
{...rest}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Input } from "@components/form/Input.js";
|
||||
import { Button } from "@components/UI/Button.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
enum LocationType {
|
||||
MGRS,
|
||||
LatLng,
|
||||
DecimalDegrees
|
||||
}
|
||||
|
||||
export const NewLocationMessage = (): JSX.Element => {
|
||||
const { connection } = useDevice();
|
||||
|
||||
return (
|
||||
<div className="m-4 w-96">
|
||||
<form
|
||||
onSubmit={(e): void => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Input label="Name" />
|
||||
<Input label="Description" />
|
||||
{/* <Select label="Type" value={LocationType.MGRS}>
|
||||
{renderOptions(LocationType)}
|
||||
</Select> */}
|
||||
<Input label="Coordinates" />
|
||||
<Button
|
||||
onClick={() => {
|
||||
void connection?.sendWaypoint(
|
||||
new Protobuf.Waypoint({
|
||||
latitudeI: Math.floor(3.89103 * 1e7),
|
||||
longitudeI: Math.floor(105.87005 * 1e7),
|
||||
name: "TEST",
|
||||
description: "This is a description"
|
||||
}),
|
||||
"broadcast"
|
||||
);
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AudioValidation } from "@app/validation/moduleConfig/audio.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Audio = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CannedMessageValidation } from "@app/validation/moduleConfig/cannedMessage.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const CannedMessage = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ExternalNotificationValidation } from "@app/validation/moduleConfig/externalNotification.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const ExternalNotification = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MQTTValidation } from "@app/validation/moduleConfig/mqtt.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
import { useDevice } from "@app/core/stores/deviceStore.js";
|
||||
|
||||
export const MQTT = (): JSX.Element => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { RangeTestValidation } from "@app/validation/moduleConfig/rangeTest.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const RangeTest = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SerialValidation } from "@app/validation/moduleConfig/serial.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Serial = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { StoreForwardValidation } from "@app/validation/moduleConfig/storeForward.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const StoreForward = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TelemetryValidation } from "@app/validation/moduleConfig/telemetry.js";
|
||||
import { useDevice } from "@core/stores/deviceStore.js";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
import { DynamicForm } from "@app/components/DynamicForm.js";
|
||||
import { DynamicForm } from "@components/DynamicForm.js";
|
||||
|
||||
export const Telemetry = (): JSX.Element => {
|
||||
const { moduleConfig, setWorkingModuleConfig } = useDevice();
|
||||
|
||||
@@ -10,10 +10,9 @@ import {
|
||||
EditIcon,
|
||||
LayoutGrid
|
||||
} from "lucide-react";
|
||||
import { Subtle } from "./UI/Typography/Subtle.js";
|
||||
import { Button } from "./UI/Button.js";
|
||||
import { SidebarSection } from "./UI/Sidebar/SidebarSection.js";
|
||||
import { SidebarButton } from "./UI/Sidebar/sidebarButton.js";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.js";
|
||||
import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.js";
|
||||
import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.js";
|
||||
|
||||
export interface SidebarProps {
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
import { cn } from "@core/utils/cn.js";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox };
|
||||
@@ -1,21 +1,50 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@core/utils/cn.js";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
action?: {
|
||||
icon: LucideIcon;
|
||||
onClick: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
({ className, prefix, suffix, action, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||
className
|
||||
<div className="relative w-full">
|
||||
{prefix && (
|
||||
<span className="inline-flex items-center rounded-l-md bg-backgroundPrimary px-3 font-mono text-sm text-textSecondary brightness-hover">
|
||||
{prefix}
|
||||
</span>
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<input
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||
action && "pr-8",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
{suffix && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 font-mono text-textSecondary">
|
||||
<span className="text-gray-500 sm:text-sm">{suffix}</span>
|
||||
</div>
|
||||
)}
|
||||
{action && (
|
||||
<button
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-400 focus:outline-none "
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<action.icon size={20} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { bitwiseDecode, bitwiseEncode, enumLike } from "@core/utils/bitwise.js";
|
||||
import { InfoWrapper } from "@components/form/InfoWrapper.js";
|
||||
// import { Listbox } from "@headlessui/react";
|
||||
import { Protobuf } from "@meshtastic/meshtasticjs";
|
||||
|
||||
export interface BitwiseSelectProps {
|
||||
label?: string;
|
||||
description?: string;
|
||||
error?: string;
|
||||
selected: number;
|
||||
decodeEnun: enumLike;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export const BitwiseSelect = ({
|
||||
label,
|
||||
description,
|
||||
error,
|
||||
selected,
|
||||
decodeEnun,
|
||||
onChange
|
||||
}: BitwiseSelectProps): JSX.Element => {
|
||||
const [decodedSelected, setDecodedSelected] = useState<string[]>([]);
|
||||
|
||||
const options = Object.entries(decodeEnun)
|
||||
.filter((value) => typeof value[1] !== "number")
|
||||
.map((value) => {
|
||||
return {
|
||||
value: parseInt(value[0]),
|
||||
label: value[1]
|
||||
.toString()
|
||||
.replace("POS_", "")
|
||||
.toLowerCase()
|
||||
.toLocaleUpperCase() //TODO: Investigate
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setDecodedSelected(
|
||||
bitwiseDecode(selected, Protobuf.Config_PositionConfig_PositionFlags).map(
|
||||
(flag) =>
|
||||
Protobuf.Config_PositionConfig_PositionFlags[flag]
|
||||
.replace("POS_", "")
|
||||
.toLowerCase()
|
||||
)
|
||||
);
|
||||
}, [selected]);
|
||||
|
||||
return (
|
||||
<InfoWrapper label={label} description={description} error={error}>
|
||||
{/* <Listbox
|
||||
value={bitwiseDecode(selected, decodeEnun)}
|
||||
onChange={(value) => {
|
||||
onChange(bitwiseEncode(value));
|
||||
}}
|
||||
multiple
|
||||
>
|
||||
<Listbox.Button
|
||||
className={`bg-orange-100 focus:ring-orange-500 flex h-10 w-full items-center gap-2 rounded-md border-transparent px-3 text-sm focus:border-transparent focus:outline-none focus:ring-2`}
|
||||
>
|
||||
{decodedSelected.map((option) => (
|
||||
<span className="bg-orange-300 rounded-md p-1">{option}</span>
|
||||
))}
|
||||
</Listbox.Button>
|
||||
<Listbox.Options>
|
||||
{options.map((option) => (
|
||||
<Listbox.Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Listbox> */}
|
||||
</InfoWrapper>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { forwardRef, InputHTMLAttributes } from "react";
|
||||
|
||||
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Input({ label, disabled, ...rest }: CheckboxProps, ref) {
|
||||
return (
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex h-5 items-center">
|
||||
<input
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
className={`h-4 w-4 rounded border-none bg-backgroundPrimary text-accent focus:outline-none focus:ring-2 focus:ring-accent ${
|
||||
disabled
|
||||
? "bg-orange-50 cursor-not-allowed text-accent brightness-disabled"
|
||||
: ""
|
||||
}`}
|
||||
disabled={disabled}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label
|
||||
className={`font-medium ${
|
||||
disabled ? "text-textSecondary" : "text-textPrimary"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export interface FormSectionProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const FormSection = ({
|
||||
title,
|
||||
children
|
||||
}: FormSectionProps): JSX.Element => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<h3 className="absolute left-2 -top-2 bg-backgroundSecondary px-1 text-lg font-medium text-textPrimary">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="mt-2 rounded-md border-2 border-backgroundPrimary p-2">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import { forwardRef, useEffect } from "react";
|
||||
import { InfoWrapper } from "@components/form/InfoWrapper.js";
|
||||
import { useState } from "react";
|
||||
import type { InputProps } from "@components/form/Input.js";
|
||||
|
||||
export const IPInput = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{
|
||||
label,
|
||||
description,
|
||||
prefix,
|
||||
suffix,
|
||||
action,
|
||||
error,
|
||||
disabled,
|
||||
value,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref
|
||||
) {
|
||||
const [numericalValue, setNumericalValue] = useState<number>();
|
||||
const [facadeInputValue, setFacadeInputValue] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof value === "number") {
|
||||
setFacadeInputValue(
|
||||
value
|
||||
.toString(16)
|
||||
.match(/.{1,3}/g)
|
||||
?.map((v) => parseInt(v, 10))
|
||||
?.join(".")
|
||||
);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<InfoWrapper label={label} description={description} error={error}>
|
||||
<div className="relative flex rounded-md">
|
||||
<input
|
||||
value={numericalValue}
|
||||
onChange={(e) => setNumericalValue(parseInt(e.target.value))}
|
||||
ref={ref}
|
||||
hidden
|
||||
/>
|
||||
<input
|
||||
value={facadeInputValue}
|
||||
onChange={(e) => {
|
||||
setFacadeInputValue(e.target.value);
|
||||
setNumericalValue(
|
||||
parseInt(
|
||||
e.target.value
|
||||
.split(".")
|
||||
.map((v) => parseInt(v).toString(16))
|
||||
.join(""),
|
||||
16
|
||||
)
|
||||
);
|
||||
}}
|
||||
className={`flex h-10 w-full rounded-md border-none bg-backgroundPrimary px-3 text-sm text-textPrimary focus:outline-none focus:ring-2 focus:ring-accent ${
|
||||
prefix ? "rounded-l-none" : ""
|
||||
} ${action ? "rounded-r-none" : ""} ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-textSecondary brightness-disabled hover:brightness-disabled"
|
||||
: ""
|
||||
}`}
|
||||
disabled={disabled}
|
||||
step="any"
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
</InfoWrapper>
|
||||
);
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export interface InfoWrapperProps {
|
||||
label?: string;
|
||||
description?: string;
|
||||
error?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const InfoWrapper = ({
|
||||
label,
|
||||
description,
|
||||
error,
|
||||
children
|
||||
}: InfoWrapperProps): JSX.Element => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Label */}
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-textPrimary">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{/* */}
|
||||
{children}
|
||||
{error && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<AlertCircleIcon size={16} className="text-red-500" />
|
||||
</div>
|
||||
)}
|
||||
{description && (
|
||||
<p className="mt-2 text-sm text-textSecondary">{description}</p>
|
||||
)}
|
||||
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import { forwardRef, InputHTMLAttributes } from "react";
|
||||
import { InfoWrapper, InfoWrapperProps } from "@components/form/InfoWrapper.js";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
|
||||
export interface InputProps
|
||||
extends InputHTMLAttributes<HTMLInputElement>,
|
||||
Omit<InfoWrapperProps, "children"> {
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
action?: {
|
||||
icon: JSX.Element;
|
||||
action: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{
|
||||
label,
|
||||
description,
|
||||
prefix,
|
||||
suffix,
|
||||
action,
|
||||
error,
|
||||
disabled,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<InfoWrapper label={label} description={description} error={error}>
|
||||
<div className="relative flex rounded-md">
|
||||
{prefix && (
|
||||
<span className="inline-flex items-center rounded-l-md bg-backgroundPrimary px-3 font-mono text-sm text-textSecondary brightness-hover">
|
||||
{prefix}
|
||||
</span>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
className={`flex h-10 w-full rounded-md border-none bg-backgroundPrimary px-3 text-sm text-textPrimary focus:outline-none focus:ring-2 focus:ring-accent ${
|
||||
prefix ? "rounded-l-none" : ""
|
||||
} ${action ? "rounded-r-none" : ""} ${
|
||||
disabled
|
||||
? "cursor-not-allowed text-textSecondary brightness-disabled hover:brightness-disabled"
|
||||
: ""
|
||||
}`}
|
||||
disabled={disabled}
|
||||
step="any"
|
||||
{...rest}
|
||||
/>
|
||||
{suffix && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 font-mono text-textSecondary">
|
||||
<span className="text-gray-500 sm:text-sm">{suffix}</span>
|
||||
</div>
|
||||
)}
|
||||
{action && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={action.action}
|
||||
className="relative -ml-px inline-flex items-center space-x-2 rounded-r-md bg-backgroundPrimary px-4 py-2 text-sm font-medium text-textSecondary brightness-hover hover:text-accent hover:brightness-hover focus:outline-none focus:ring-2 focus:ring-accent active:brightness-press"
|
||||
>
|
||||
{action.icon}
|
||||
</button>
|
||||
)}
|
||||
{error && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<AlertCircleIcon size={16} className="text-red-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</InfoWrapper>
|
||||
);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Switch } from "../UI/Switch.js";
|
||||
import { InfoWrapper } from "./InfoWrapper.js";
|
||||
|
||||
export interface ToggleProps {
|
||||
checked: boolean;
|
||||
label?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export const Toggle = ({
|
||||
checked,
|
||||
label,
|
||||
description,
|
||||
disabled,
|
||||
onChange
|
||||
}: ToggleProps): JSX.Element => {
|
||||
return (
|
||||
<InfoWrapper label={label} description={description}>
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
</InfoWrapper>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user