Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53fcc6997c | ||
|
|
69e64356bb |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import type { GridItemHTMLElement } from "@homarr/gridstack";
|
||||
import { WidgetSizeProvider } from "@homarr/widgets";
|
||||
|
||||
import type { DynamicSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
|
||||
import { BoardItemContent } from "../items/item-content";
|
||||
@@ -18,7 +19,7 @@ export const SectionContent = () => {
|
||||
* @see https://github.com/homarr-labs/homarr/pull/1770
|
||||
*/
|
||||
const sortedItems = useMemo(() => {
|
||||
return [...items, ...innerSections].sort((itemA, itemB) => {
|
||||
return [...items, ...innerSections].toSorted((itemA, itemB) => {
|
||||
if (itemA.yOffset === itemB.yOffset) {
|
||||
return itemA.xOffset - itemB.xOffset;
|
||||
}
|
||||
@@ -59,7 +60,13 @@ const Item = ({ item, innerRef }: ItemProps) => {
|
||||
minWidth={minWidth}
|
||||
minHeight={minHeight}
|
||||
>
|
||||
{item.type === "item" ? <BoardItemContent item={item} /> : <BoardDynamicSection section={item} />}
|
||||
{item.type === "item" ? (
|
||||
<WidgetSizeProvider gridWidth={item.width} gridHeight={item.height}>
|
||||
<BoardItemContent item={item} />
|
||||
</WidgetSizeProvider>
|
||||
) : (
|
||||
<BoardDynamicSection section={item} />
|
||||
)}
|
||||
</GridStackItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@ import * as weather from "./weather";
|
||||
|
||||
export type { WidgetDefinition, WidgetOptionsSettings } from "./definition";
|
||||
export type { WidgetComponentProps };
|
||||
export { useWidgetSize, WidgetSizeProvider } from "./widget-size";
|
||||
|
||||
export const widgetImports = {
|
||||
clock,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { metricToImperial } from "@homarr/common";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { useWidgetSize } from "../widget-size";
|
||||
import { WeatherDescription, WeatherIcon } from "./icon";
|
||||
|
||||
export default function WeatherWidget({ isEditMode, options }: WidgetComponentProps<"weather">) {
|
||||
@@ -29,19 +30,23 @@ export default function WeatherWidget({ isEditMode, options }: WidgetComponentPr
|
||||
onData: (data) => utils.widget.weather.atLocation.setData(input, data),
|
||||
});
|
||||
|
||||
const { gridWidth, gridHeight } = useWidgetSize();
|
||||
const isCompact = gridWidth <= 1 || gridHeight <= 1;
|
||||
const showForecast = options.hasForecast && !isCompact;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
gap="sm"
|
||||
gap={isCompact ? "xs" : "sm"}
|
||||
justify="center"
|
||||
w="100%"
|
||||
h="100%"
|
||||
style={{ pointerEvents: isEditMode ? "none" : undefined }}
|
||||
>
|
||||
{options.hasForecast ? (
|
||||
{showForecast ? (
|
||||
<WeeklyForecast weather={weather} options={options} />
|
||||
) : (
|
||||
<DailyWeather weather={weather} options={options} />
|
||||
<DailyWeather weather={weather} options={options} compact={isCompact} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
@@ -51,24 +56,28 @@ interface WeatherProps extends Pick<WidgetComponentProps<"weather">, "options">
|
||||
weather: RouterOutputs["widget"]["weather"]["atLocation"];
|
||||
}
|
||||
|
||||
const DailyWeather = ({ options, weather }: WeatherProps) => {
|
||||
const DailyWeather = ({ options, weather, compact }: WeatherProps & { compact?: boolean }) => {
|
||||
const t = useScopedI18n("widget.weather");
|
||||
const tCommon = useScopedI18n("common");
|
||||
|
||||
const iconSize = compact ? 20 : 30;
|
||||
const tempSize = compact ? 20 : 30;
|
||||
const detailSize = compact ? 13 : 16;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group className="weather-day-group" gap="sm">
|
||||
<HoverCard>
|
||||
<HoverCard.Target>
|
||||
<Box>
|
||||
<WeatherIcon size={30} code={weather.current.weathercode} />
|
||||
<WeatherIcon size={iconSize} code={weather.current.weathercode} />
|
||||
</Box>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<WeatherDescription weatherOnly weatherCode={weather.current.weathercode} />
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
<Text fz={30}>
|
||||
<Text fz={tempSize}>
|
||||
{getPreferredUnit(
|
||||
weather.current.temperature,
|
||||
options.isFormatFahrenheit,
|
||||
@@ -77,10 +86,10 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
|
||||
</Text>
|
||||
</Group>
|
||||
<Stack gap="xs" align="center">
|
||||
{options.showCurrentWindSpeed && (
|
||||
{options.showCurrentWindSpeed && !compact && (
|
||||
<Group className="weather-current-wind-speed-group" wrap="nowrap" gap="xs">
|
||||
<IconWind size={16} />
|
||||
<Text fz={16}>
|
||||
<IconWind size={detailSize} />
|
||||
<Text fz={detailSize}>
|
||||
{t("currentWindSpeed", {
|
||||
currentWindSpeed: (options.useImperialSpeed
|
||||
? metricToImperial(weather.current.windspeed)
|
||||
@@ -95,8 +104,8 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
|
||||
)}
|
||||
<Group className="weather-max-min-temp-group" wrap="nowrap" gap="sm">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconArrowUpRight size={16} />
|
||||
<Text fz={16}>
|
||||
<IconArrowUpRight size={detailSize} />
|
||||
<Text fz={detailSize}>
|
||||
{getPreferredUnit(
|
||||
weather.daily[0]?.maxTemp,
|
||||
options.isFormatFahrenheit,
|
||||
@@ -105,8 +114,8 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconArrowDownRight size={16} />
|
||||
<Text fz={16}>
|
||||
<IconArrowDownRight size={detailSize} />
|
||||
<Text fz={detailSize}>
|
||||
{getPreferredUnit(
|
||||
weather.daily[0]?.minTemp,
|
||||
options.isFormatFahrenheit,
|
||||
@@ -116,11 +125,11 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
{options.showCity && (
|
||||
{options.showCity && !compact && (
|
||||
<>
|
||||
<Group className="weather-city-group" wrap="nowrap" gap="xs">
|
||||
<IconMapPin size={16} />
|
||||
<Text fz={16} style={{ whiteSpace: "nowrap" }}>
|
||||
<IconMapPin size={detailSize} />
|
||||
<Text fz={detailSize} style={{ whiteSpace: "nowrap" }}>
|
||||
{options.location.name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
export interface WidgetSize {
|
||||
gridWidth: number;
|
||||
gridHeight: number;
|
||||
}
|
||||
|
||||
const WidgetSizeContext = createContext<WidgetSize | null>(null);
|
||||
|
||||
export const useWidgetSize = () => {
|
||||
const ctx = useContext(WidgetSizeContext);
|
||||
if (!ctx) throw new Error("useWidgetSize must be used within WidgetSizeProvider");
|
||||
return ctx;
|
||||
};
|
||||
|
||||
interface WidgetSizeProviderProps extends WidgetSize {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const WidgetSizeProvider = ({ gridWidth, gridHeight, children }: WidgetSizeProviderProps) => (
|
||||
<WidgetSizeContext.Provider value={{ gridWidth, gridHeight }}>{children}</WidgetSizeContext.Provider>
|
||||
);
|
||||
Reference in new issue
Block a user