mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-27 17:00:54 +01:00
fix(dns-controls): improve responsive styles (#2534)
* fix(dns-controls): improve responsive styles * fix: further dns-control improvements
This commit is contained in:
@@ -3,7 +3,20 @@
|
||||
import "../../widgets-common.css";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ActionIcon, Badge, Button, Card, Flex, ScrollArea, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||
import {
|
||||
ActionIcon,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Flex,
|
||||
Group,
|
||||
Indicator,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconCircleFilled, IconClockPause, IconPlayerPlay, IconPlayerStop } from "@tabler/icons-react";
|
||||
import combineClasses from "clsx";
|
||||
@@ -30,6 +43,7 @@ export default function DnsHoleControlsWidget({
|
||||
options,
|
||||
integrationIds,
|
||||
isEditMode,
|
||||
width,
|
||||
}: WidgetComponentProps<typeof widgetKind>) {
|
||||
const board = useRequiredBoard();
|
||||
// DnsHole integrations with interaction permissions
|
||||
@@ -177,10 +191,10 @@ export default function DnsHoleControlsWidget({
|
||||
const controlAllButtonsVisible = options.showToggleAllButtons && integrationsWithInteractions.length > 0;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
<Stack
|
||||
className="dns-hole-controls-stack"
|
||||
justify="space-between"
|
||||
h="100%"
|
||||
direction="column"
|
||||
p="sm"
|
||||
gap="sm"
|
||||
style={{ pointerEvents: isEditMode ? "none" : undefined }}
|
||||
@@ -189,23 +203,25 @@ export default function DnsHoleControlsWidget({
|
||||
<Flex className="dns-hole-controls-buttons" gap="sm">
|
||||
<Tooltip label={t("widget.dnsHoleControls.controls.enableAll")}>
|
||||
<Button
|
||||
size="xs"
|
||||
p={0}
|
||||
className="dns-hole-controls-enable-all-button"
|
||||
onClick={() => integrationsSummaries.disabled.forEach((integrationId) => toggleDns(integrationId))}
|
||||
disabled={integrationsSummaries.disabled.length === 0}
|
||||
variant="light"
|
||||
color="green"
|
||||
h="fit-content"
|
||||
p="xs"
|
||||
bd={0}
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconPlayerPlay className="dns-hole-controls-enable-all-icon" size={24} />
|
||||
<IconPlayerPlay className="dns-hole-controls-enable-all-icon" size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("widget.dnsHoleControls.controls.setTimer")}>
|
||||
<Button
|
||||
size="xs"
|
||||
p={0}
|
||||
className="dns-hole-controls-timer-all-button"
|
||||
onClick={() => {
|
||||
setSelectedIntegrationIds(integrationsSummaries.enabled);
|
||||
@@ -214,36 +230,34 @@ export default function DnsHoleControlsWidget({
|
||||
disabled={integrationsSummaries.enabled.length === 0}
|
||||
variant="light"
|
||||
color="yellow"
|
||||
h="fit-content"
|
||||
p="xs"
|
||||
bd={0}
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconClockPause className="dns-hole-controls-timer-all-icon" size={24} />
|
||||
<IconClockPause className="dns-hole-controls-timer-all-icon" size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("widget.dnsHoleControls.controls.disableAll")}>
|
||||
<Button
|
||||
size="xs"
|
||||
p={0}
|
||||
className="dns-hole-controls-disable-all-button"
|
||||
onClick={() => integrationsSummaries.enabled.forEach((integrationId) => toggleDns(integrationId))}
|
||||
disabled={integrationsSummaries.enabled.length === 0}
|
||||
variant="light"
|
||||
color="red"
|
||||
h="fit-content"
|
||||
p="xs"
|
||||
bd={0}
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconPlayerStop className="dns-hole-controls-disable-all-icon" size={24} />
|
||||
<IconPlayerStop className="dns-hole-controls-disable-all-icon" size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<ScrollArea className="dns-hole-controls-integration-list-scroll-area flexed-scroll-area">
|
||||
<ScrollArea.Autosize className="dns-hole-controls-integration-list-scroll-area flexed-scroll-area">
|
||||
<Stack
|
||||
className="dns-hole-controls-integration-list"
|
||||
gap="sm"
|
||||
@@ -260,10 +274,11 @@ export default function DnsHoleControlsWidget({
|
||||
open={open}
|
||||
t={t}
|
||||
hasIconColor={board.iconColor !== null}
|
||||
rootWidth={width}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
</ScrollArea.Autosize>
|
||||
|
||||
<TimerModal
|
||||
opened={opened}
|
||||
@@ -271,7 +286,7 @@ export default function DnsHoleControlsWidget({
|
||||
selectedIntegrationIds={selectedIntegrationIds}
|
||||
disableDns={disableDns}
|
||||
/>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -283,6 +298,7 @@ interface ControlsCardProps {
|
||||
open: () => void;
|
||||
t: TranslationFunction;
|
||||
hasIconColor: boolean;
|
||||
rootWidth: number;
|
||||
}
|
||||
|
||||
const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
@@ -293,6 +309,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
open,
|
||||
t,
|
||||
hasIconColor,
|
||||
rootWidth,
|
||||
}) => {
|
||||
const isConnected = useIntegrationConnected(data.integration.updatedAt, { timeout: 30000 });
|
||||
const isEnabled = data.summary.status ? data.summary.status === "enabled" : undefined;
|
||||
@@ -302,95 +319,161 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
const board = useRequiredBoard();
|
||||
|
||||
const iconUrl = integrationDefs[data.integration.kind].iconUrl;
|
||||
const layout = rootWidth < 256 ? "sm" : "md";
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={combineClasses(
|
||||
"dns-hole-controls-integration-item-outer-shell",
|
||||
`dns-hole-controls-integration-item-${data.integration.id}`,
|
||||
`dns-hole-controls-integration-item-${data.integration.name}`,
|
||||
classes.card,
|
||||
)}
|
||||
key={data.integration.id}
|
||||
p="sm"
|
||||
py={8}
|
||||
radius={board.itemRadius}
|
||||
<Indicator
|
||||
disabled={!isConnected || layout !== "sm"}
|
||||
color={dnsLightStatus(isEnabled)}
|
||||
position="top-end"
|
||||
offset={14}
|
||||
>
|
||||
<Flex className="dns-hole-controls-item-container" gap="md" align="center" direction="row">
|
||||
<MaskedOrNormalImage
|
||||
imageUrl={iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
alt={data.integration.name}
|
||||
className="dns-hole-controls-item-icon"
|
||||
style={{
|
||||
height: 30,
|
||||
width: 30,
|
||||
filter: !isConnected ? "grayscale(100%)" : undefined,
|
||||
}}
|
||||
/>
|
||||
<Flex className="dns-hole-controls-item-data-stack" direction="column" gap={5}>
|
||||
<Text className="dns-hole-controls-item-integration-name" fz="md" fw={"bold"}>
|
||||
{data.integration.name}
|
||||
</Text>
|
||||
<Flex className="dns-hole-controls-item-controls" direction="row" gap="lg">
|
||||
<UnstyledButton
|
||||
className="dns-hole-controls-item-toggle-button"
|
||||
disabled={!controlEnabled}
|
||||
display="contents"
|
||||
style={{ cursor: controlEnabled ? "pointer" : "default" }}
|
||||
onClick={() => toggleDns(data.integration.id)}
|
||||
>
|
||||
<Badge
|
||||
className={`dns-hole-controls-item-toggle-button-styling${controlEnabled ? " hoverable-component clickable-component" : ""}`}
|
||||
bd="1px solid var(--border-color)"
|
||||
px="sm"
|
||||
h="lg"
|
||||
color="var(--background-color)"
|
||||
c="var(--mantine-color-text)"
|
||||
styles={{ section: { marginInlineEnd: "sm" }, root: { cursor: "inherit" } }}
|
||||
leftSection={
|
||||
isConnected && (
|
||||
<IconCircleFilled
|
||||
className="dns-hole-controls-item-status-icon"
|
||||
color={dnsLightStatus(isEnabled)}
|
||||
size={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{t(
|
||||
`widget.dnsHoleControls.controls.${
|
||||
!isConnected
|
||||
? "disconnected"
|
||||
: typeof isEnabled === "undefined"
|
||||
? "processing"
|
||||
: isEnabled
|
||||
? "enabled"
|
||||
: "disabled"
|
||||
}`,
|
||||
)}
|
||||
</Badge>
|
||||
</UnstyledButton>
|
||||
<Card
|
||||
className={combineClasses(
|
||||
"dns-hole-controls-integration-item-outer-shell",
|
||||
`dns-hole-controls-integration-item-${data.integration.id}`,
|
||||
`dns-hole-controls-integration-item-${data.integration.name}`,
|
||||
classes.card,
|
||||
)}
|
||||
key={data.integration.id}
|
||||
p="sm"
|
||||
py={8}
|
||||
radius={board.itemRadius}
|
||||
>
|
||||
<Flex className="dns-hole-controls-item-container" gap="md" align="center" direction="row" w="100%">
|
||||
{layout === "md" && (
|
||||
<MaskedOrNormalImage
|
||||
imageUrl={iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
alt={data.integration.name}
|
||||
className="dns-hole-controls-item-icon"
|
||||
style={{
|
||||
height: 30,
|
||||
width: 30,
|
||||
filter: !isConnected ? "grayscale(100%)" : undefined,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Flex className="dns-hole-controls-item-data-stack" direction="column" w="100%" gap={5}>
|
||||
<Group gap="xs" align="center" wrap="nowrap">
|
||||
{layout === "sm" && (
|
||||
<MaskedOrNormalImage
|
||||
imageUrl={iconUrl}
|
||||
hasColor={hasIconColor}
|
||||
alt={data.integration.name}
|
||||
className="dns-hole-controls-item-icon"
|
||||
style={{
|
||||
height: 16,
|
||||
width: 16,
|
||||
filter: !isConnected ? "grayscale(100%)" : undefined,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Text className="dns-hole-controls-item-integration-name" fz="sm">
|
||||
{data.integration.name}
|
||||
</Text>
|
||||
</Group>
|
||||
<Flex className="dns-hole-controls-item-controls" direction="row" gap="sm" w="100%">
|
||||
{layout === "sm" && (
|
||||
<Group gap="xs" grow wrap="nowrap" w="100%">
|
||||
{!isEnabled ? (
|
||||
<ActionIcon
|
||||
onClick={() => toggleDns(data.integration.id)}
|
||||
disabled={!controlEnabled}
|
||||
size="sm"
|
||||
color="green"
|
||||
variant="light"
|
||||
>
|
||||
<IconPlayerPlay size={12} />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<ActionIcon
|
||||
onClick={() => toggleDns(data.integration.id)}
|
||||
disabled={!controlEnabled}
|
||||
size="sm"
|
||||
color="red"
|
||||
variant="light"
|
||||
>
|
||||
<IconPlayerStop size={12} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setSelectedIntegrationIds([data.integration.id]);
|
||||
open();
|
||||
}}
|
||||
size="sm"
|
||||
color="yellow"
|
||||
variant="light"
|
||||
>
|
||||
<IconClockPause size={12} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)}
|
||||
{layout === "md" && (
|
||||
<UnstyledButton
|
||||
className="dns-hole-controls-item-toggle-button"
|
||||
disabled={!controlEnabled}
|
||||
display="contents"
|
||||
style={{ cursor: controlEnabled ? "pointer" : "default" }}
|
||||
onClick={() => toggleDns(data.integration.id)}
|
||||
>
|
||||
<Badge
|
||||
className={`dns-hole-controls-item-toggle-button-styling${controlEnabled ? " hoverable-component clickable-component" : ""}`}
|
||||
bd="1px solid var(--border-color)"
|
||||
px="sm"
|
||||
h="lg"
|
||||
color="var(--background-color)"
|
||||
c="var(--mantine-color-text)"
|
||||
styles={{ section: { marginInlineEnd: "sm" }, root: { cursor: "inherit" } }}
|
||||
leftSection={
|
||||
isConnected && (
|
||||
<IconCircleFilled
|
||||
className="dns-hole-controls-item-status-icon"
|
||||
color={dnsLightStatus(isEnabled)}
|
||||
size={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{t(
|
||||
`widget.dnsHoleControls.controls.${
|
||||
!isConnected
|
||||
? "disconnected"
|
||||
: typeof isEnabled === "undefined"
|
||||
? "processing"
|
||||
: isEnabled
|
||||
? "enabled"
|
||||
: "disabled"
|
||||
}`,
|
||||
)}
|
||||
</Badge>
|
||||
</UnstyledButton>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{layout === "md" && (
|
||||
<ActionIcon
|
||||
className="dns-hole-controls-item-timer-button"
|
||||
display={isInteractPermitted ? undefined : "none"}
|
||||
disabled={!controlEnabled || !isEnabled}
|
||||
color="yellow"
|
||||
size={30}
|
||||
radius={board.itemRadius}
|
||||
bd={0}
|
||||
ms={"auto"}
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setSelectedIntegrationIds([data.integration.id]);
|
||||
open();
|
||||
}}
|
||||
>
|
||||
<IconClockPause className="dns-hole-controls-item-timer-icon" size={20} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Flex>
|
||||
<ActionIcon
|
||||
className="dns-hole-controls-item-timer-button"
|
||||
display={isInteractPermitted ? undefined : "none"}
|
||||
disabled={!controlEnabled || !isEnabled}
|
||||
color="yellow"
|
||||
size={30}
|
||||
radius={board.itemRadius}
|
||||
bd={0}
|
||||
ms={"auto"}
|
||||
variant="subtle"
|
||||
onClick={() => {
|
||||
setSelectedIntegrationIds([data.integration.id]);
|
||||
open();
|
||||
}}
|
||||
>
|
||||
<IconClockPause className="dns-hole-controls-item-timer-icon" size={20} />
|
||||
</ActionIcon>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Card>
|
||||
</Indicator>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -62,14 +62,3 @@
|
||||
.text-flash {
|
||||
animation: glow 1s ease-in-out;
|
||||
}
|
||||
|
||||
/*To apply to any ScrollArea that we want to flex. Same weird workaround as before*/
|
||||
.flexed-scroll-area {
|
||||
height: 100%;
|
||||
.mantine-ScrollArea-viewport {
|
||||
& div[style="min-width: 100%; display: table;"] {
|
||||
display: flex !important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user