mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-03 05:09:16 +01:00
style: improve mobile compatibility of certain manage pages (#678)
* style: improve mobile compatibility of certain manage pages * style: improve mobile support for more manage pages * fix: format issues * chore: address pull request feedback
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
.bannerContainer {
|
||||
padding: 3rem;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(
|
||||
|
||||
@@ -38,17 +38,17 @@ export const HeroBanner = () => {
|
||||
const gridSpan = 12 / countIconGroups;
|
||||
|
||||
return (
|
||||
<Box className={classes.bannerContainer} bg="dark.6" pos="relative">
|
||||
<Box className={classes.bannerContainer} p={{ base: "lg", md: "3rem" }} bg="dark.6" pos="relative">
|
||||
<Stack gap={0}>
|
||||
<Title order={2} c="dimmed">
|
||||
<Title fz={{ base: "h4", md: "h2" }} c="dimmed">
|
||||
Welcome back to your
|
||||
</Title>
|
||||
<Group gap="xs">
|
||||
<Image src="/logo/logo.png" w={40} h={40} />
|
||||
<Title>Homarr Dashboard</Title>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Image src="/logo/logo.png" w={{ base: 32, md: 40 }} h={{ base: 32, md: 40 }} />
|
||||
<Title fz={{ base: "h3", md: "h1" }}>Homarr Board</Title>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Box className={classes.scrollContainer} w={"30%"} top={0} right={0} pos="absolute">
|
||||
<Box visibleFrom="md" className={classes.scrollContainer} w={"30%"} top={0} right={0} pos="absolute">
|
||||
<Grid>
|
||||
{Array(countIconGroups)
|
||||
.fill(0)
|
||||
|
||||
@@ -49,7 +49,7 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => {
|
||||
}, [app, mutate, t, openConfirmModal]);
|
||||
|
||||
return (
|
||||
<ActionIcon loading={isPending} variant="subtle" color="red" onClick={onClick} aria-label="Delete app">
|
||||
<ActionIcon loading={isPending} variant="subtle" color="red" onClick={onClick} aria-label={t("title")}>
|
||||
<IconTrash color="red" size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
);
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ActionIcon,
|
||||
ActionIconGroup,
|
||||
Anchor,
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconApps, IconPencil } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||
import { AppDeleteButton } from "./_app-delete-button";
|
||||
|
||||
export default async function AppsPage() {
|
||||
@@ -25,13 +15,13 @@ export default async function AppsPage() {
|
||||
const t = await getScopedI18n("app");
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ManageContainer>
|
||||
<Stack>
|
||||
<Group justify="space-between" align="center">
|
||||
<Title>{t("page.list.title")}</Title>
|
||||
<Button component={Link} href="/manage/apps/new">
|
||||
<MobileAffixButton component={Link} href="/manage/apps/new">
|
||||
{t("page.create.title")}
|
||||
</Button>
|
||||
</MobileAffixButton>
|
||||
</Group>
|
||||
{apps.length === 0 && <AppNoResults />}
|
||||
{apps.length > 0 && (
|
||||
@@ -42,7 +32,7 @@ export default async function AppsPage() {
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,10 +40,12 @@ interface AppCardProps {
|
||||
app: RouterOutputs["app"]["all"][number];
|
||||
}
|
||||
|
||||
const AppCard = ({ app }: AppCardProps) => {
|
||||
const AppCard = async ({ app }: AppCardProps) => {
|
||||
const t = await getScopedI18n("app");
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Group justify="space-between">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group align="top" justify="start" wrap="nowrap">
|
||||
<Avatar
|
||||
size="sm"
|
||||
@@ -66,14 +58,16 @@ const AppCard = ({ app }: AppCardProps) => {
|
||||
}}
|
||||
/>
|
||||
<Stack gap={0}>
|
||||
<Text fw={500}>{app.name}</Text>
|
||||
<Text fw={500} lineClamp={1}>
|
||||
{app.name}
|
||||
</Text>
|
||||
{app.description && (
|
||||
<Text size="sm" c="gray.6">
|
||||
<Text size="sm" c="gray.6" lineClamp={4}>
|
||||
{app.description}
|
||||
</Text>
|
||||
)}
|
||||
{app.href && (
|
||||
<Anchor href={app.href} size="sm" w="min-content">
|
||||
<Anchor href={app.href} lineClamp={1} size="sm" w="min-content">
|
||||
{app.href}
|
||||
</Anchor>
|
||||
)}
|
||||
@@ -86,7 +80,7 @@ const AppCard = ({ app }: AppCardProps) => {
|
||||
href={`/manage/apps/edit/${app.id}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label="Edit app"
|
||||
aria-label={t("page.edit.title")}
|
||||
>
|
||||
<IconPencil size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@mantine/core";
|
||||
import { IconCategoryPlus } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -10,6 +9,7 @@ import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
||||
import { AddBoardModal } from "~/components/manage/boards/add-board-modal";
|
||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||
|
||||
interface CreateBoardButtonProps {
|
||||
boardNames: string[];
|
||||
@@ -37,8 +37,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
|
||||
}, [mutateAsync, boardNames, openModal]);
|
||||
|
||||
return (
|
||||
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={onClick} loading={isPending}>
|
||||
<MobileAffixButton leftSection={<IconCategoryPlus size="1rem" />} onClick={onClick} loading={isPending}>
|
||||
{t("management.page.board.action.new.label")}
|
||||
</Button>
|
||||
</MobileAffixButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Group,
|
||||
Menu,
|
||||
MenuTarget,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
@@ -22,6 +23,7 @@ import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
import { getBoardPermissionsAsync } from "~/components/board/permissions/server";
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown";
|
||||
import { CreateBoardButton } from "./_components/create-board-button";
|
||||
|
||||
@@ -31,20 +33,22 @@ export default async function ManageBoardsPage() {
|
||||
const boards = await api.board.getAllBoards();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group justify="space-between">
|
||||
<Title mb="md">{t("title")}</Title>
|
||||
<CreateBoardButton boardNames={boards.map((board) => board.name)} />
|
||||
</Group>
|
||||
<ManageContainer>
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title mb="md">{t("title")}</Title>
|
||||
<CreateBoardButton boardNames={boards.map((board) => board.name)} />
|
||||
</Group>
|
||||
|
||||
<Grid>
|
||||
{boards.map((board) => (
|
||||
<GridCol span={{ base: 12, md: 6, xl: 4 }} key={board.id}>
|
||||
<BoardCard board={board} />
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
<Grid mb={{ base: "xl", md: 0 }}>
|
||||
{boards.map((board) => (
|
||||
<GridCol span={{ base: 12, md: 6 }} key={board.id}>
|
||||
<BoardCard board={board} />
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export const DeleteIntegrationActionButton = ({ count, integration }: DeleteInte
|
||||
},
|
||||
});
|
||||
}}
|
||||
aria-label="Delete integration"
|
||||
aria-label={t("title")}
|
||||
>
|
||||
<IconTrash color="red" size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { ChangeEvent } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Group, Menu, ScrollArea, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { Flex, Group, Menu, ScrollArea, Text, TextInput } from "@mantine/core";
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
|
||||
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||
@@ -25,7 +25,7 @@ export const IntegrationCreateDropdownContent = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Flex direction={{ base: "column-reverse", md: "column" }} gap="sm">
|
||||
<TextInput
|
||||
leftSection={<IconSearch stroke={1.5} size={20} />}
|
||||
placeholder={t("integration.page.list.search")}
|
||||
@@ -47,6 +47,6 @@ export const IntegrationCreateDropdownContent = () => {
|
||||
) : (
|
||||
<Menu.Item disabled>{t("common.noResults")}</Menu.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Fragment } from "react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
AccordionControl,
|
||||
@@ -5,9 +7,11 @@ import {
|
||||
AccordionPanel,
|
||||
ActionIcon,
|
||||
ActionIconGroup,
|
||||
Affix,
|
||||
Anchor,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Divider,
|
||||
Group,
|
||||
Menu,
|
||||
MenuDropdown,
|
||||
@@ -22,7 +26,7 @@ import {
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { IconChevronDown, IconPencil } from "@tabler/icons-react";
|
||||
import { IconChevronDown, IconChevronUp, IconPencil } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -32,6 +36,7 @@ import { getIntegrationName } from "@homarr/definitions";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
import { CountBadge } from "@homarr/ui";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { ActiveTabAccordion } from "../../../../components/active-tab-accordion";
|
||||
import { IntegrationAvatar } from "./_integration-avatar";
|
||||
import { DeleteIntegrationActionButton } from "./_integration-buttons";
|
||||
@@ -48,26 +53,47 @@ export default async function IntegrationsPage({ searchParams }: IntegrationsPag
|
||||
const t = await getScopedI18n("integration");
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ManageContainer>
|
||||
<Stack>
|
||||
<Group justify="space-between" align="center">
|
||||
<Title>{t("page.list.title")}</Title>
|
||||
<Menu width={256} trapFocus position="bottom-start" withinPortal shadow="md" keepMounted={false}>
|
||||
<MenuTarget>
|
||||
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
|
||||
</MenuTarget>
|
||||
<MenuDropdown>
|
||||
<IntegrationCreateDropdownContent />
|
||||
</MenuDropdown>
|
||||
</Menu>
|
||||
|
||||
<Box>
|
||||
<IntegrationSelectMenu>
|
||||
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
|
||||
<MenuTarget>
|
||||
<Button rightSection={<IconChevronUp size={16} stroke={1.5} />}>{t("action.create")}</Button>
|
||||
</MenuTarget>
|
||||
</Affix>
|
||||
</IntegrationSelectMenu>
|
||||
</Box>
|
||||
|
||||
<Box visibleFrom="md">
|
||||
<IntegrationSelectMenu>
|
||||
<MenuTarget>
|
||||
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
|
||||
</MenuTarget>
|
||||
</IntegrationSelectMenu>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<IntegrationList integrations={integrations} activeTab={searchParams.tab} />
|
||||
</Stack>
|
||||
</Container>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const IntegrationSelectMenu = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<Menu width={256} trapFocus position="bottom-end" withinPortal shadow="md" keepMounted={false}>
|
||||
{children}
|
||||
<MenuDropdown>
|
||||
<IntegrationCreateDropdownContent />
|
||||
</MenuDropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
interface IntegrationListProps {
|
||||
integrations: RouterOutputs["integration"]["all"];
|
||||
activeTab?: IntegrationKind;
|
||||
@@ -105,7 +131,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
</Group>
|
||||
</AccordionControl>
|
||||
<AccordionPanel>
|
||||
<Table>
|
||||
<Table visibleFrom="md">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>{t("field.name.label")}</TableTh>
|
||||
@@ -130,7 +156,7 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
href={`/manage/integrations/edit/${integration.id}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label="Edit integration"
|
||||
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
||||
>
|
||||
<IconPencil size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
@@ -142,6 +168,34 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
|
||||
<Stack gap="xs" hiddenFrom="md">
|
||||
{integrations.map((integration, index) => (
|
||||
<Fragment key={integration.id}>
|
||||
{index !== 0 && <Divider />}
|
||||
<Stack gap={0}>
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Text>{integration.name}</Text>
|
||||
<ActionIconGroup>
|
||||
<ActionIcon
|
||||
component={Link}
|
||||
href={`/manage/integrations/edit/${integration.id}`}
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label={t("page.edit.title", { name: getIntegrationName(integration.kind) })}
|
||||
>
|
||||
<IconPencil size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
<DeleteIntegrationActionButton integration={integration} count={integrations.length} />
|
||||
</ActionIconGroup>
|
||||
</Group>
|
||||
<Anchor href={integration.url} target="_blank" rel="noreferrer" size="sm">
|
||||
{integration.url}
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
))}
|
||||
</Stack>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
|
||||
@@ -72,8 +72,8 @@ export default async function ManagementPage() {
|
||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||
{links.map((link, index) => (
|
||||
<Card component={Link} href={link.href} key={`link-${index}`} withBorder>
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group wrap="nowrap">
|
||||
<Text size="2.4rem" fw="bolder">
|
||||
{link.count}
|
||||
</Text>
|
||||
|
||||
@@ -109,7 +109,9 @@ const SwitchSetting = ({
|
||||
<UnstyledButton style={{ flexGrow: 1 }} onClick={handleClick}>
|
||||
<Stack gap={0}>
|
||||
<Text fw="bold">{title}</Text>
|
||||
<Text c="gray.5">{text}</Text>
|
||||
<Text c="gray.5" fz={{ base: "xs", md: "sm" }}>
|
||||
{text}
|
||||
</Text>
|
||||
</Stack>
|
||||
</UnstyledButton>
|
||||
<Switch disabled={disabled} onClick={handleClick} checked={form.values[formKey] && !disabled} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Button, Container, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconSettings, IconShieldLock } from "@tabler/icons-react";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -9,6 +9,7 @@ import { auth } from "@homarr/auth/next";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { catchTrpcNotFound } from "~/errors/trpc-not-found";
|
||||
import { NavigationLink } from "../groups/[id]/_navigation";
|
||||
import { canAccessUserEditPage } from "./access";
|
||||
@@ -28,7 +29,7 @@ export default async function Layout({ children, params }: PropsWithChildren<Lay
|
||||
}
|
||||
|
||||
return (
|
||||
<Container size="xl">
|
||||
<ManageContainer size="xl">
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Group justify="space-between" align="center">
|
||||
@@ -64,6 +65,6 @@ export default async function Layout({ children, params }: PropsWithChildren<Lay
|
||||
</GridCol>
|
||||
<GridCol span={{ xs: 12, md: 8, lg: 9, xl: 10 }}>{children}</GridCol>
|
||||
</Grid>
|
||||
</Container>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button, Container, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { Button, Grid, GridCol, Group, Stack, Text, Title } from "@mantine/core";
|
||||
import { IconLock, IconSettings, IconUsersGroup } from "@tabler/icons-react";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getI18n, getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { NavigationLink } from "./_navigation";
|
||||
|
||||
interface LayoutProps {
|
||||
@@ -18,7 +19,7 @@ export default async function Layout({ children, params }: PropsWithChildren<Lay
|
||||
const group = await api.group.getById({ id: params.id });
|
||||
|
||||
return (
|
||||
<Container size="xl">
|
||||
<ManageContainer size="xl">
|
||||
<Grid>
|
||||
<GridCol span={12}>
|
||||
<Group justify="space-between" align="center">
|
||||
@@ -54,6 +55,6 @@ export default async function Layout({ children, params }: PropsWithChildren<Lay
|
||||
</GridCol>
|
||||
<GridCol span={{ xs: 12, md: 8, lg: 9, xl: 10 }}>{children}</GridCol>
|
||||
</Grid>
|
||||
</Container>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
@@ -9,6 +8,7 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { UserSelectModal } from "~/app/[locale]/boards/[name]/settings/_access/user-select-modal";
|
||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||
|
||||
interface AddGroupMemberProps {
|
||||
groupId: string;
|
||||
@@ -40,8 +40,8 @@ export const AddGroupMember = ({ groupId, presentUserIds }: AddGroupMemberProps)
|
||||
}, [openModal, presentUserIds, groupId, mutateAsync, tMembersAdd]);
|
||||
|
||||
return (
|
||||
<Button color="teal" onClick={handleAddMember}>
|
||||
<MobileAffixButton color="teal" onClick={handleAddMember}>
|
||||
{tMembersAdd("label")}
|
||||
</Button>
|
||||
</MobileAffixButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ interface PermissionRowProps {
|
||||
|
||||
const PermissionRow = ({ name, label, description }: PermissionRowProps) => {
|
||||
return (
|
||||
<Group justify="space-between" align="center">
|
||||
<Group justify="space-between" align="center" wrap="nowrap">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500}>{label}</Text>
|
||||
<Text c="gray.5">{description}</Text>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useI18n } from "@homarr/translation/client";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
||||
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
|
||||
|
||||
export const AddGroup = () => {
|
||||
const t = useI18n();
|
||||
@@ -21,9 +22,9 @@ export const AddGroup = () => {
|
||||
}, [openModal]);
|
||||
|
||||
return (
|
||||
<Button onClick={handleAddGroup} color="teal">
|
||||
<MobileAffixButton onClick={handleAddGroup} color="teal">
|
||||
{t("group.action.create.label")}
|
||||
</Button>
|
||||
</MobileAffixButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Anchor,
|
||||
Container,
|
||||
Group,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { Anchor, Group, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from "@mantine/core";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -19,6 +7,7 @@ import { getI18n } from "@homarr/translation/server";
|
||||
import { SearchInput, TablePagination, UserAvatarGroup } from "@homarr/ui";
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { ManageContainer } from "~/components/manage/manage-container";
|
||||
import { AddGroup } from "./_add-group";
|
||||
|
||||
const searchParamsSchema = z.object({
|
||||
@@ -41,7 +30,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) {
|
||||
const { items: groups, totalCount } = await api.group.getPaginated(searchParams);
|
||||
|
||||
return (
|
||||
<Container size="xl">
|
||||
<ManageContainer size="xl">
|
||||
<Stack>
|
||||
<Title>{t("group.title")}</Title>
|
||||
<Group justify="space-between">
|
||||
@@ -72,7 +61,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) {
|
||||
<TablePagination total={Math.ceil(totalCount / searchParams.pageSize)} />
|
||||
</Group>
|
||||
</Stack>
|
||||
</Container>
|
||||
</ManageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
11
apps/nextjs/src/components/manage/manage-container.tsx
Normal file
11
apps/nextjs/src/components/manage/manage-container.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import type { MantineSize } from "@mantine/core";
|
||||
import { Container } from "@mantine/core";
|
||||
|
||||
export const ManageContainer = ({ children, size }: PropsWithChildren<{ size?: MantineSize }>) => {
|
||||
return (
|
||||
<Container size={size} px={{ base: "0 !important", md: "var(--mantine-spacing-md) !important" }}>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
16
apps/nextjs/src/components/manage/mobile-affix-button.tsx
Normal file
16
apps/nextjs/src/components/manage/mobile-affix-button.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { forwardRef } from "react";
|
||||
import type { ButtonProps } from "@mantine/core";
|
||||
import { Affix, Button, createPolymorphicComponent } from "@mantine/core";
|
||||
|
||||
type MobileAffixButtonProps = Omit<ButtonProps, "visibleFrom" | "hiddenFrom">;
|
||||
|
||||
export const MobileAffixButton = createPolymorphicComponent<"button", MobileAffixButtonProps>(
|
||||
forwardRef<HTMLButtonElement, MobileAffixButtonProps>((props, ref) => (
|
||||
<>
|
||||
<Button ref={ref} visibleFrom="md" {...props} />
|
||||
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
|
||||
<Button ref={ref} {...props} />
|
||||
</Affix>
|
||||
</>
|
||||
)),
|
||||
);
|
||||
Reference in New Issue
Block a user