mirror of
https://github.com/meshtastic/web.git
synced 2026-03-12 10:56:13 -04:00
WIP config update
This commit is contained in:
@@ -13,8 +13,8 @@ lint:
|
||||
- oxipng@8.0.0
|
||||
- svgo@3.0.2
|
||||
- git-diff-check
|
||||
- actionlint@1.6.22
|
||||
- gitleaks@8.15.2
|
||||
- actionlint@1.6.23
|
||||
- gitleaks@8.15.3
|
||||
runtimes:
|
||||
enabled:
|
||||
- go@1.18.3
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PeersPage } from "@pages/Peers.js";
|
||||
export const PageRouter = (): JSX.Element => {
|
||||
const { activePage } = useDevice();
|
||||
return (
|
||||
<div className="flex-grow overflow-y-auto border-l-2 border-backgroundPrimary">
|
||||
<div className="flex-grow overflow-y-auto bg-backgroundPrimary">
|
||||
{activePage === "messages" && <MessagesPage />}
|
||||
{activePage === "map" && <MapPage />}
|
||||
{activePage === "extensions" && <ExtensionsPage />}
|
||||
|
||||
@@ -7,15 +7,15 @@ import { PageNav } from "@app/Nav/PageNav.js";
|
||||
import { Mono } from "@components/generic/Mono.js";
|
||||
import { Hashicon } from "@emeraldpay/hashicon-react";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { MoonIcon, SunIcon } from "@primer/octicons-react";
|
||||
|
||||
export const DeviceSelector = (): JSX.Element => {
|
||||
const { getDevices } = useDeviceStore();
|
||||
const { selectedDevice, setSelectedDevice, darkMode } = useAppStore();
|
||||
const { selectedDevice, setSelectedDevice, darkMode, setDarkMode } = useAppStore();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-14 items-center gap-3 bg-backgroundPrimary pt-3 [writing-mode:vertical-rl]">
|
||||
<div className="flex items-center gap-3">
|
||||
<Mono className="select-none">Connected Devices</Mono>
|
||||
<span className="flex font-bold text-textPrimary">
|
||||
{getDevices().map((device) => (
|
||||
<div
|
||||
@@ -55,7 +55,12 @@ export const DeviceSelector = (): JSX.Element => {
|
||||
|
||||
<NavSpacer />
|
||||
|
||||
<div>//actions</div>
|
||||
<div onClick={() => setDarkMode(!darkMode)} className="bg-backgroundPrimary py-5 px-4 hover:brightness-hover active:brightness-press text-textSecondary hover:text-textPrimary">{
|
||||
darkMode ? (
|
||||
<SunIcon className="w-4" />
|
||||
) : (
|
||||
<MoonIcon className="w-4" />
|
||||
)}</div>
|
||||
|
||||
<img
|
||||
src={darkMode ? "Logo_White.svg" : "Logo_Black.svg"}
|
||||
|
||||
@@ -39,16 +39,6 @@ export const NewDevice = () => {
|
||||
<div className="m-auto h-96 w-96">
|
||||
<TabbedContent
|
||||
tabs={tabs}
|
||||
actions={[
|
||||
{
|
||||
icon: darkMode ? (
|
||||
<SunIcon className="w-4" />
|
||||
) : (
|
||||
<MoonIcon className="w-4" />
|
||||
),
|
||||
action: () => setDarkMode(!darkMode)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -49,17 +49,7 @@ export const Map = (): JSX.Element => {
|
||||
// }, [reset, rasterSources]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Map Config"
|
||||
breadcrumbs={["App Config", "Map"]}
|
||||
reset={() =>
|
||||
reset({
|
||||
rasterSources
|
||||
})
|
||||
}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<InfoWrapper label="WMS Sources">
|
||||
<div className="flex flex-col gap-2">
|
||||
{fields.map((field, index) => (
|
||||
|
||||
@@ -105,31 +105,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Channel Editor"
|
||||
breadcrumbs={[
|
||||
"Channels",
|
||||
channel.settings?.name.length
|
||||
? channel.settings.name
|
||||
: channel.role === Protobuf.Channel_Role.PRIMARY
|
||||
? "Primary"
|
||||
: `Channel: ${channel.index}`
|
||||
]}
|
||||
reset={() =>
|
||||
reset({
|
||||
enabled: [
|
||||
Protobuf.Channel_Role.SECONDARY,
|
||||
Protobuf.Channel_Role.PRIMARY
|
||||
].find((role) => role === channel?.role)
|
||||
? true
|
||||
: false,
|
||||
...channel?.settings,
|
||||
psk: fromByteArray(channel?.settings?.psk ?? new Uint8Array(0))
|
||||
})
|
||||
}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
{channel?.index !== 0 && (
|
||||
<>
|
||||
<Controller
|
||||
|
||||
@@ -71,10 +71,6 @@ export const Bluetooth = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Bluetooth Config"
|
||||
breadcrumbs={["Config", "Bluetooth"]}
|
||||
reset={() => reset(config.bluetooth)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Controller
|
||||
|
||||
@@ -64,10 +64,6 @@ export const Device = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Device Config"
|
||||
breadcrumbs={["Config", "Device"]}
|
||||
reset={() => reset(config.device)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Select
|
||||
|
||||
@@ -7,7 +7,7 @@ import { toast } from "react-hot-toast";
|
||||
import { Input } from "@app/components/form/Input.js";
|
||||
import { Select } from "@app/components/form/Select.js";
|
||||
import { Toggle } from "@app/components/form/Toggle.js";
|
||||
import { DisplayValidation } from "@app/validation/config/display.js";
|
||||
import type { DisplayValidation } from "@app/validation/config/display.js";
|
||||
import { Form } from "@components/form/Form";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { renderOptions } from "@core/utils/selectEnumOptions.js";
|
||||
@@ -22,54 +22,48 @@ export const Display = (): JSX.Element => {
|
||||
formState: { errors, isDirty },
|
||||
reset,
|
||||
control
|
||||
} = useForm<Protobuf.Config_DisplayConfig>({
|
||||
defaultValues: config.display,
|
||||
resolver: classValidatorResolver(DisplayValidation)
|
||||
} = useForm<DisplayValidation>({
|
||||
defaultValues: config.display
|
||||
// resolver: classValidatorResolver(DisplayValidation)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset(config.display);
|
||||
}, [reset, config.display]);
|
||||
// useEffect(() => {
|
||||
// reset(config.display);
|
||||
// }, [reset, config.display]);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
if (connection) {
|
||||
void toast.promise(
|
||||
connection
|
||||
.setConfig(
|
||||
new Protobuf.Config({
|
||||
payloadVariant: {
|
||||
case: "display",
|
||||
value: data
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() =>
|
||||
setConfig(
|
||||
new Protobuf.Config({
|
||||
payloadVariant: {
|
||||
case: "display",
|
||||
value: data
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
{
|
||||
loading: "Saving...",
|
||||
success: "Saved Display Config, Restarting Node",
|
||||
error: "No response received"
|
||||
}
|
||||
);
|
||||
}
|
||||
// if (connection) {
|
||||
// void toast.promise(
|
||||
// connection
|
||||
// .setConfig(
|
||||
// new Protobuf.Config({
|
||||
// payloadVariant: {
|
||||
// case: "display",
|
||||
// value: data
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// .then(() =>
|
||||
// setConfig(
|
||||
// new Protobuf.Config({
|
||||
// payloadVariant: {
|
||||
// case: "display",
|
||||
// value: data
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// ),
|
||||
// {
|
||||
// loading: "Saving...",
|
||||
// success: "Saved Display Config, Restarting Node",
|
||||
// error: "No response received"
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Display Config"
|
||||
breadcrumbs={["Config", "Display"]}
|
||||
reset={() => reset(config.display)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Input
|
||||
label="Screen Timeout"
|
||||
description="Turn off the display after this long"
|
||||
|
||||
@@ -72,10 +72,6 @@ export const LoRa = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="LoRa Config"
|
||||
breadcrumbs={["Config", "LoRa"]}
|
||||
reset={() => reset(config.lora)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<FormSection title="Modem Settings">
|
||||
|
||||
@@ -85,10 +85,6 @@ export const Network = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Network Config"
|
||||
breadcrumbs={["Config", "Network"]}
|
||||
reset={() => reset(config.network)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<ErrorMessage errors={errors} name="wifiEnabled" />
|
||||
|
||||
@@ -108,10 +108,6 @@ export const Position = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Position Config"
|
||||
breadcrumbs={["Config", "Position"]}
|
||||
reset={() => reset(config.position)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Controller
|
||||
|
||||
@@ -63,10 +63,6 @@ export const Power = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Power Config"
|
||||
breadcrumbs={["Config", "Power"]}
|
||||
reset={() => reset(config.power)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Input
|
||||
|
||||
@@ -62,16 +62,6 @@ export const User = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="User Config"
|
||||
breadcrumbs={["Config", "User"]}
|
||||
reset={() => {
|
||||
reset({
|
||||
longName: myNode?.data.user?.longName,
|
||||
shortName: myNode?.data.user?.shortName,
|
||||
isLicensed: myNode?.data.user?.isLicensed
|
||||
});
|
||||
}}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<ErrorMessage errors={errors} name="longName" />
|
||||
|
||||
@@ -63,13 +63,7 @@ export const Audio = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Audio Config"
|
||||
breadcrumbs={["Module Config", "Audio"]}
|
||||
reset={() => reset(moduleConfig.audio)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="codec2Enabled"
|
||||
control={control}
|
||||
|
||||
@@ -69,13 +69,7 @@ export const CannedMessage = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Canned Message Config"
|
||||
breadcrumbs={["Module Config", "Canned Message"]}
|
||||
reset={() => reset(moduleConfig.cannedMessage)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
|
||||
@@ -66,13 +66,7 @@ export const ExternalNotification = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="External Notification Config"
|
||||
breadcrumbs={["Module Config", "External Notification"]}
|
||||
reset={() => reset(moduleConfig.externalNotification)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
|
||||
@@ -67,13 +67,7 @@ export const MQTT = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="MQTT Config"
|
||||
breadcrumbs={["Module Config", "MQTT"]}
|
||||
reset={() => reset(moduleConfig.mqtt)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
|
||||
@@ -68,10 +68,6 @@ export const RangeTest = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Range Test Config"
|
||||
breadcrumbs={["Module Config", "Range Test"]}
|
||||
reset={() => reset(moduleConfig.rangeTest)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Controller
|
||||
|
||||
@@ -69,13 +69,7 @@ export const Serial = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Serial Config"
|
||||
breadcrumbs={["Module Config", "Serial"]}
|
||||
reset={() => reset(moduleConfig.serial)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
|
||||
@@ -67,13 +67,7 @@ export const StoreForward = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Store & Forward Config"
|
||||
breadcrumbs={["Module Config", "Store & Forward"]}
|
||||
reset={() => reset(moduleConfig.storeForward)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
|
||||
@@ -61,13 +61,7 @@ export const Telemetry = (): JSX.Element => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
title="Telemetry Config"
|
||||
breadcrumbs={["Module Config", "Telemetry"]}
|
||||
reset={() => reset(moduleConfig.telemetry)}
|
||||
dirty={isDirty}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Controller
|
||||
name="environmentMeasurementEnabled"
|
||||
control={control}
|
||||
|
||||
@@ -21,10 +21,7 @@ export const DeviceWidget = ({
|
||||
}: DeviceWidgetProps): JSX.Element => {
|
||||
return (
|
||||
<div className="relative flex shrink-0 flex-col overflow-hidden rounded-md text-sm text-textPrimary">
|
||||
<div className="absolute bottom-20 h-full w-full">
|
||||
<Hashicon size={350} value={nodeNum} />
|
||||
</div>
|
||||
<div className="backdrop-brightness-50 flex p-3 backdrop-blur-md backdrop-hue-rotate-30">
|
||||
<div className="bg-backgroundPrimary flex p-3">
|
||||
<div>
|
||||
<Hashicon size={96} value={nodeNum} />
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Button = ({
|
||||
}: ButtonProps): JSX.Element => {
|
||||
return (
|
||||
<button
|
||||
className={`flex w-full rounded-md bg-accentMuted px-3 text-textPrimary hover:brightness-hover focus:outline-none active:brightness-press ${
|
||||
className={`flex w-full select-none rounded-md bg-accentMuted px-3 text-textPrimary hover:brightness-hover focus:outline-none active:brightness-press ${
|
||||
size === "sm"
|
||||
? "h-8 text-sm"
|
||||
: size === "md"
|
||||
|
||||
@@ -1,73 +1,22 @@
|
||||
import type React from "react";
|
||||
import type { HTMLProps } from "react";
|
||||
|
||||
import { Button } from "@components/form/Button.js";
|
||||
import {
|
||||
ArrowRightCircleIcon,
|
||||
ArrowUturnLeftIcon,
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
HomeIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
export interface FormProps extends HTMLProps<HTMLFormElement> {
|
||||
title: string;
|
||||
breadcrumbs: string[];
|
||||
reset: () => void;
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
export const Form = ({
|
||||
title,
|
||||
breadcrumbs,
|
||||
reset,
|
||||
dirty,
|
||||
children,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps): JSX.Element => {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
<form className="w-full px-2" onSubmit={onSubmit} {...props}>
|
||||
<div className="select-none rounded-md bg-backgroundPrimary p-4">
|
||||
<ol className="flex gap-4 text-textSecondary">
|
||||
<li className="cursor-pointer hover:brightness-disabled">
|
||||
<HomeIcon className="h-5 w-5 flex-shrink-0" />
|
||||
</li>
|
||||
{breadcrumbs.map((breadcrumb, index) => (
|
||||
<li key={index} className="flex gap-4">
|
||||
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 brightness-disabled" />
|
||||
<span className="cursor-pointer text-sm font-medium hover:brightness-disabled">
|
||||
{breadcrumb}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<div className="mt-2 flex items-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-textPrimary">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
reset();
|
||||
}}
|
||||
iconBefore={<ArrowUturnLeftIcon className="w-4" />}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!dirty}
|
||||
iconBefore={<CheckIcon className="w-4" />}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 p-2">{children}</div>
|
||||
<form
|
||||
className="mr-2 w-full rounded-md bg-backgroundSecondary px-2"
|
||||
onSubmit={onSubmit}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col gap-3 p-4">{children}</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ export const TabbedContent = ({
|
||||
actions
|
||||
}: TabbedContentProps): JSX.Element => {
|
||||
return (
|
||||
<Tab.Group as="div" className="flex flex-grow flex-col gap-2">
|
||||
<Tab.Group as="div" className="flex flex-grow flex-col">
|
||||
<Tab.List className="flex bg-backgroundPrimary">
|
||||
{tabs.map((entry, index) => (
|
||||
<Tab key={index} disabled={entry.disabled}>
|
||||
|
||||
@@ -11,6 +11,9 @@ import { Power } from "@components/PageComponents/Config/Power.js";
|
||||
import { User } from "@components/PageComponents/Config/User.js";
|
||||
import { useDevice } from "@core/providers/useDevice.js";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { ChevronRightIcon, HomeIcon } from "@heroicons/react/24/outline";
|
||||
import { Button } from "@app/components/form/Button.js";
|
||||
import { CheckIcon } from "@primer/octicons-react";
|
||||
|
||||
export const DeviceConfig = (): JSX.Element => {
|
||||
const { hardware } = useDevice();
|
||||
@@ -52,31 +55,53 @@ export const DeviceConfig = (): JSX.Element => {
|
||||
];
|
||||
|
||||
return (
|
||||
<Tab.Group as="div" className="flex w-full gap-3">
|
||||
<Tab.List className="flex w-44 flex-col gap-1">
|
||||
{configSections.map((Config, index) => (
|
||||
<Tab key={index} as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
className={`flex cursor-pointer items-center rounded-md px-3 py-2 text-sm font-medium ${
|
||||
selected
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{Config.label}
|
||||
</div>
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels as={Fragment}>
|
||||
{configSections.map((Config, index) => (
|
||||
<Tab.Panel key={index} as={Fragment}>
|
||||
<Config.element />
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
<div className="w-full">
|
||||
<div className="m-2 flex rounded-md bg-backgroundSecondary p-2">
|
||||
<ol className="my-auto ml-2 flex gap-4 text-textSecondary">
|
||||
<li className="cursor-pointer hover:brightness-disabled">
|
||||
<HomeIcon className="h-5 w-5 flex-shrink-0" />
|
||||
</li>
|
||||
{["Config", "User"].map((breadcrumb, index) => (
|
||||
<li key={index} className="flex gap-4">
|
||||
<ChevronRightIcon className="h-5 w-5 flex-shrink-0 brightness-disabled" />
|
||||
<span className="cursor-pointer text-sm font-medium hover:brightness-disabled">
|
||||
{breadcrumb}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<div className="ml-auto">
|
||||
<Button iconBefore={<CheckIcon className="w-4" />}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tab.Group as="div" className="flex w-full gap-3">
|
||||
<Tab.List className="flex w-44 flex-col">
|
||||
{configSections.map((Config, index) => (
|
||||
<Tab key={index} as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<div
|
||||
className={`flex cursor-pointer items-center border-l-4 p-4 text-sm font-medium ${
|
||||
selected
|
||||
? "border-accent bg-accentMuted bg-opacity-10 text-textPrimary"
|
||||
: "border-backgroundPrimary text-textSecondary"
|
||||
}`}
|
||||
>
|
||||
{Config.label}
|
||||
<span className="ml-auto bg-accent rounded-full px-3 text-textPrimary">3</span>
|
||||
</div>
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels as={Fragment}>
|
||||
{configSections.map((Config, index) => (
|
||||
<Tab.Panel key={index} as={Fragment}>
|
||||
<Config.element />
|
||||
</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user