more type safe tabs

This commit is contained in:
mbecker20
2025-10-19 12:27:55 -07:00
parent 4f8d1c22cc
commit feb263c15f
9 changed files with 55 additions and 44 deletions

View File

@@ -10,8 +10,10 @@ import { ResourceComponents } from "..";
import { DeploymentTable } from "../deployment/table";
import { BuildConfig } from "./config";
type BuildTabsView = "Config" | "Info" | "Deployments";
export const BuildTabs = ({ id }: { id: string }) => {
const [view, setView] = useLocalStorage<"Config" | "Info" | "Deployments">(
const [view, setView] = useLocalStorage<BuildTabsView>(
"build-tabs-v1",
"Config"
);
@@ -20,7 +22,7 @@ export const BuildTabs = ({ id }: { id: string }) => {
);
const deploymentsDisabled = (deployments?.length || 0) === 0;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<BuildTabsView>[]>(
() => [
{
value: "Config",
@@ -46,6 +48,8 @@ export const BuildTabs = ({ id }: { id: string }) => {
);
switch (view) {
case "Config":
return <BuildConfig id={id} titleOther={Selector} />;
case "Info":
return <BuildInfo id={id} titleOther={Selector} />;
case "Deployments":
@@ -57,7 +61,5 @@ export const BuildTabs = ({ id }: { id: string }) => {
<DeploymentTable deployments={deployments ?? []} />
</Section>
);
default:
return <BuildConfig id={id} titleOther={Selector} />;
}
};

View File

@@ -18,14 +18,17 @@ export const DeploymentTabs = ({ id }: { id: string }) => {
return <DeploymentTabsInner deployment={deployment} />;
};
type DeploymentTabsView = "Config" | "Log" | "Inspect" | "Terminal";
const DeploymentTabsInner = ({
deployment,
}: {
deployment: Types.DeploymentListItem;
}) => {
const [_view, setView] = useLocalStorage<
"Config" | "Log" | "Inspect" | "Terminal"
>("deployment-tabs-v1", "Config");
const [_view, setView] = useLocalStorage<DeploymentTabsView>(
"deployment-tabs-v1",
"Config"
);
const { specificLogs, specificInspect, specificTerminal } = usePermissions({
type: "Deployment",
id: deployment.id,
@@ -55,7 +58,7 @@ const DeploymentTabsInner = ({
? "Config"
: _view;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<DeploymentTabsView>[]>(
() => [
{
value: "Config",
@@ -109,13 +112,13 @@ const DeploymentTabsInner = ({
);
switch (view) {
case "Config":
return <DeploymentConfig id={deployment.id} titleOther={Selector} />;
case "Log":
return <DeploymentLogs id={deployment.id} titleOther={Selector} />;
case "Inspect":
return <DeploymentInspect id={deployment.id} titleOther={Selector} />;
case "Terminal":
return <ContainerTerminal query={terminalQuery} titleOther={Selector} />;
default:
return <DeploymentConfig id={deployment.id} titleOther={Selector} />;
}
};

View File

@@ -38,7 +38,7 @@ export const ServerInfo = ({
);
}
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<ServerInfoView>[]>(
() => [
{
value: "Containers",

View File

@@ -48,7 +48,7 @@ export const ServerTabs = ({ id }: { id: string }) => {
const noResources = noDeployments && noRepos && noStacks;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<ServerTabView>[]>(
() => [
{
value: "Config",
@@ -88,6 +88,8 @@ export const ServerTabs = ({ id }: { id: string }) => {
);
switch (view) {
case "Config":
return <ServerConfig id={id} titleOther={Selector} />;
case "Stats":
return <ServerStats id={id} titleOther={Selector} />;
case "Docker":
@@ -96,8 +98,6 @@ export const ServerTabs = ({ id }: { id: string }) => {
return <ServerTabsResources id={id} Selector={Selector} />;
case "Terminals":
return <ServerTabsTerminals id={id} Selector={Selector} />;
default:
return <ServerConfig id={id} titleOther={Selector} />;
}
};

View File

@@ -11,10 +11,13 @@ import { StackServices } from "./services";
import { StackLogs } from "./log";
import { StackConfig } from "./config";
type StackTabsView = "Config" | "Info" | "Services" | "Log";
export const StackTabs = ({ id }: { id: string }) => {
const [_view, setView] = useLocalStorage<
"Config" | "Info" | "Services" | "Log"
>("stack-tabs-v1", "Config");
const [_view, setView] = useLocalStorage<StackTabsView>(
"stack-tabs-v1",
"Config"
);
const info = useStack(id)?.info;
const { specific, specificLogs } = usePermissions({ type: "Stack", id });
@@ -34,7 +37,7 @@ export const StackTabs = ({ id }: { id: string }) => {
? "Config"
: _view;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<StackTabsView>[]>(
() => [
{
value: "Config",
@@ -44,7 +47,7 @@ export const StackTabs = ({ id }: { id: string }) => {
hidden: hideInfo,
},
{
value: "Service",
value: "Services",
disabled: hideServices,
},
{
@@ -66,13 +69,13 @@ export const StackTabs = ({ id }: { id: string }) => {
);
switch (view) {
case "Config":
return <StackConfig id={id} titleOther={Selector} />;
case "Info":
return <StackInfo id={id} titleOther={Selector} />;
case "Services":
return <StackServices id={id} titleOther={Selector} />;
case "Log":
return <StackLogs id={id} titleOther={Selector} />;
default:
return <StackConfig id={id} titleOther={Selector} />;
}
};

View File

@@ -14,7 +14,7 @@ import { ResourceSyncConfig } from "./config";
type ResourceSyncTabsView = "Config" | "Info" | "Execute" | "Commit";
const syncTabsViewAtom = atomWithStorage<ResourceSyncTabsView>(
"sync-tabs-v4",
"sync-tabs-v5",
"Config"
);
@@ -58,7 +58,7 @@ export const SyncTabs = ({ id }: { id: string }) => {
const { view, setView, hideInfo, showPending } =
useResourceSyncTabsView(sync);
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<ResourceSyncTabsView>[]>(
() => [
{
value: "Config",
@@ -90,13 +90,13 @@ export const SyncTabs = ({ id }: { id: string }) => {
);
switch (view) {
case "Config":
return <ResourceSyncConfig id={id} titleOther={Selector} />;
case "Info":
return <ResourceSyncInfo id={id} titleOther={Selector} />;
case "Execute":
return <ResourceSyncPending id={id} titleOther={Selector} />;
case "Commit":
return <ResourceSyncPending id={id} titleOther={Selector} />;
default:
return <ResourceSyncConfig id={id} titleOther={Selector} />;
}
};

View File

@@ -249,7 +249,7 @@ const ContainerTabs = ({
? "Log"
: _view;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<ContainerTabsView>[]>(
() => [
{
value: "Log",

View File

@@ -261,7 +261,7 @@ const StackServiceTabs = ({
? "Log"
: _view;
const tabsNoContent = useMemo<TabNoContent[]>(
const tabsNoContent = useMemo<TabNoContent<StackServiceTabsView>[]>(
() => [
{
value: "Log",

View File

@@ -9,21 +9,24 @@ import {
} from "./select";
import { cn } from "@lib/utils";
export type Tab = {
export type Tab<Value extends string = string> = {
label?: string;
hidden?: boolean;
disabled?: boolean;
value: string;
value: Value;
content: ReactNode;
};
export type TabNoContent = Omit<Tab, "content">;
export type TabNoContent<Value extends string = string> = Omit<
Tab<Value>,
"content"
>;
export const MobileFriendlyTabs = (props: {
tabs: Tab[];
export const MobileFriendlyTabs = <Value extends string = string>(props: {
tabs: Tab<Value>[];
actions?: ReactNode;
value: string;
onValueChange: (value: string) => void;
value: Value;
onValueChange: (value: Value) => void;
}) => {
return (
<MobileFriendlyTabsWrapper
@@ -34,15 +37,15 @@ export const MobileFriendlyTabs = (props: {
);
};
export const MobileFriendlyTabsWrapper = ({
export const MobileFriendlyTabsWrapper = <Value extends string = string>({
Selector,
tabs,
value,
className,
}: {
Selector: ReactNode;
tabs: Tab[];
value: string;
tabs: Tab<Value>[];
value: Value;
className?: string;
}) => {
return (
@@ -53,24 +56,24 @@ export const MobileFriendlyTabsWrapper = ({
);
};
export const MobileFriendlyTabsSelector = ({
export const MobileFriendlyTabsSelector = <Value extends string = string>({
tabs: _tabs,
actions,
value,
onValueChange,
tabsTriggerClassname,
}: {
tabs: TabNoContent[];
tabs: TabNoContent<Value>[];
actions?: ReactNode;
value: string;
onValueChange: (value: string) => void;
value: Value;
onValueChange: (value: Value) => void;
tabsTriggerClassname?: string;
}) => {
const tabs = _tabs.filter((t) => !t.hidden);
return (
<>
<div className="hidden md:flex items-center justify-between">
<Tabs value={value} onValueChange={onValueChange}>
<Tabs value={value} onValueChange={onValueChange as any}>
<TabsList className="justify-start w-fit">
{tabs.map((tab) => (
<TabsTrigger
@@ -107,12 +110,12 @@ export const MobileFriendlyTabsSelector = ({
);
};
export const MobileFriendlyTabsContent = ({
export const MobileFriendlyTabsContent = <Value extends string = string>({
tabs,
value,
}: {
tabs: Tab[];
value: string;
value: Value;
}) => {
return tabs.find((tab) => tab.value === value)?.content;
};