mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-01 19:17:41 +03:00
docs: add more icon support
This commit is contained in:
parent
2420fdc4ce
commit
d007bd0a6f
@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useColorMode } from "@docusaurus/theme-common";
|
||||||
import { devices } from "../config";
|
import { devices } from "../config";
|
||||||
import type { DeviceConfig } from "../config";
|
import type { DeviceConfig } from "../config";
|
||||||
import styles from "../styles.module.css";
|
import styles from "../styles.module.css";
|
||||||
@ -8,6 +9,99 @@ interface Props {
|
|||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the icon type from the icon string:
|
||||||
|
* - Starts with "<svg" → inline SVG
|
||||||
|
* - Starts with "/" or "http" → image URL/path
|
||||||
|
* - Otherwise → emoji text
|
||||||
|
*/
|
||||||
|
function getIconType(icon: string): "svg" | "image" | "emoji" {
|
||||||
|
const trimmed = icon.trim();
|
||||||
|
if (trimmed.startsWith("<svg")) return "svg";
|
||||||
|
if (trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://")) return "image";
|
||||||
|
return "emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the style object contains background-* properties,
|
||||||
|
* indicating the image should be rendered as a CSS background-image
|
||||||
|
* rather than an <img> tag.
|
||||||
|
*/
|
||||||
|
function hasBackgroundProps(style: React.CSSProperties | undefined): boolean {
|
||||||
|
if (!style) return false;
|
||||||
|
return Object.keys(style).some((key) => {
|
||||||
|
const k = key.toLowerCase().replace(/-/g, "");
|
||||||
|
return k === "backgroundsize" || k === "backgroundposition" || k === "backgroundrepeat" || k === "backgroundimage";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a style object to CSS custom properties (e.g. { width: "24px" } → { "--svg-width": "24px" })
|
||||||
|
* so they can be consumed by CSS rules targeting child elements like <svg>.
|
||||||
|
*/
|
||||||
|
function toCssVars(style: React.CSSProperties | undefined, prefix: string): React.CSSProperties {
|
||||||
|
if (!style) return {};
|
||||||
|
const vars: Record<string, string> = {};
|
||||||
|
for (const [key, value] of Object.entries(style)) {
|
||||||
|
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||||
|
vars[`--${prefix}-${cssKey}`] = value;
|
||||||
|
}
|
||||||
|
return vars as React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeviceIcon({ device }: { device: DeviceConfig }) {
|
||||||
|
const { isDarkTheme } = useColorMode();
|
||||||
|
const iconStr = isDarkTheme && device.iconDark ? device.iconDark : device.icon;
|
||||||
|
const iconStyle = (isDarkTheme && device.iconDarkStyle
|
||||||
|
? device.iconDarkStyle
|
||||||
|
: device.iconStyle) as React.CSSProperties | undefined;
|
||||||
|
const svgStyle = (isDarkTheme && device.svgDarkStyle
|
||||||
|
? device.svgDarkStyle
|
||||||
|
: device.svgStyle) as React.CSSProperties | undefined;
|
||||||
|
|
||||||
|
const iconType = getIconType(iconStr);
|
||||||
|
|
||||||
|
if (iconType === "svg") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.deviceIconSvg}
|
||||||
|
style={{ ...iconStyle, ...toCssVars(svgStyle, "svg") }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: iconStr }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconType === "image") {
|
||||||
|
// When iconStyle contains background-* properties, render as background-image
|
||||||
|
// on the container div instead of an <img> tag, enabling background-size/position control.
|
||||||
|
if (hasBackgroundProps(iconStyle)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.deviceIconImage}
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${iconStr})`,
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundSize: "contain",
|
||||||
|
...iconStyle,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.deviceIconImage}>
|
||||||
|
<img src={iconStr} alt={device.name} style={iconStyle} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.deviceIcon} style={iconStyle}>
|
||||||
|
{iconStr}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function DeviceCard({
|
function DeviceCard({
|
||||||
device,
|
device,
|
||||||
active,
|
active,
|
||||||
@ -27,7 +121,7 @@ function DeviceCard({
|
|||||||
if (e.key === "Enter" || e.key === " ") onClick();
|
if (e.key === "Enter" || e.key === " ") onClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.deviceIcon}>{device.icon}</div>
|
<DeviceIcon device={device} />
|
||||||
<div className={styles.deviceName}>{device.name}</div>
|
<div className={styles.deviceName}>{device.name}</div>
|
||||||
<div className={styles.deviceDesc}>{device.description}</div>
|
<div className={styles.deviceDesc}>{device.description}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -42,8 +42,41 @@ export interface DeviceConfig {
|
|||||||
name: string;
|
name: string;
|
||||||
/** Short description */
|
/** Short description */
|
||||||
description: string;
|
description: string;
|
||||||
/** Icon emoji or identifier */
|
/**
|
||||||
|
* Icon for the device card. Supports:
|
||||||
|
* - Emoji string (e.g. "🖥️")
|
||||||
|
* - Image URL or static path (e.g. "/img/intel.svg", "https://example.com/icon.png")
|
||||||
|
* - Inline SVG markup (e.g. "<svg>...</svg>")
|
||||||
|
*/
|
||||||
icon: string;
|
icon: string;
|
||||||
|
/**
|
||||||
|
* Additional CSS properties applied to the icon element.
|
||||||
|
* - For image-type icons: if any `background-*` property (e.g. `background-size`,
|
||||||
|
* `background-position`) is present, the image is rendered as a CSS `background-image`
|
||||||
|
* on the container div, enabling full background positioning control.
|
||||||
|
* Otherwise the image is rendered as an `<img>` tag and styles apply to it.
|
||||||
|
* - For emoji/SVG icons: styles apply to the container div.
|
||||||
|
*/
|
||||||
|
iconStyle?: Record<string, string>;
|
||||||
|
/**
|
||||||
|
* Additional CSS properties applied directly to the inner `<svg>` element
|
||||||
|
* when the icon is an inline SVG. Use this to override the default
|
||||||
|
* `width: 100%; height: 100%` or set `fill`, `transform`, etc.
|
||||||
|
* Ignored for emoji and image-type icons.
|
||||||
|
*/
|
||||||
|
svgStyle?: Record<string, string>;
|
||||||
|
/**
|
||||||
|
* Icon for dark mode. Same format as `icon`. When provided, this icon
|
||||||
|
* replaces `icon` when the user is in dark mode.
|
||||||
|
*/
|
||||||
|
iconDark?: string;
|
||||||
|
/** Additional CSS properties for the dark mode icon container */
|
||||||
|
iconDarkStyle?: Record<string, string>;
|
||||||
|
/**
|
||||||
|
* SVG-specific styles for dark mode. Same as `svgStyle` but applied
|
||||||
|
* when dark mode is active. Merged over `svgStyle` in dark mode.
|
||||||
|
*/
|
||||||
|
svgDarkStyle?: Record<string, string>;
|
||||||
/** Docker image tag, e.g. "stable" */
|
/** Docker image tag, e.g. "stable" */
|
||||||
imageTag: string;
|
imageTag: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -180,6 +180,40 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deviceIconSvg {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
height: 40px;
|
||||||
|
width: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: visible;
|
||||||
|
/* Allow iconStyle width/height to override */
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deviceIconSvg svg {
|
||||||
|
width: var(--svg-width, 100%);
|
||||||
|
height: var(--svg-height, 100%);
|
||||||
|
fill: var(--svg-fill, currentColor);
|
||||||
|
transform: var(--svg-transform, none);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deviceIconImage {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
height: 40px;
|
||||||
|
width: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deviceIconImage img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.deviceName {
|
.deviceName {
|
||||||
font-weight: var(--ifm-font-weight-semibold);
|
font-weight: var(--ifm-font-weight-semibold);
|
||||||
color: var(--ifm-font-color-base);
|
color: var(--ifm-font-color-base);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user