style(frontend): Prettier config + one-time repo-wide format pass
Prettier 3 (pinned), printWidth 100, double quotes — chosen to match the existing hand-formatting and minimise reflow. This commit is ONLY the mechanical format pass (`prettier --write`) plus the config/ignore/scripts, isolated so it never pollutes a feature diff. Verified behaviour-neutral: typecheck, eslint (0 errors), and vitest all green before and after. See .git-blame-ignore-revs — `git blame` skips this sha.
This commit is contained in:
@@ -126,8 +126,14 @@ function subShadowCss(px: number, color: string): string {
|
||||
const o = px;
|
||||
const b = Math.max(1, Math.round(px / 2));
|
||||
const dirs: [number, number][] = [
|
||||
[o, 0], [-o, 0], [0, o], [0, -o],
|
||||
[o, o], [o, -o], [-o, o], [-o, -o],
|
||||
[o, 0],
|
||||
[-o, 0],
|
||||
[0, o],
|
||||
[0, -o],
|
||||
[o, o],
|
||||
[o, -o],
|
||||
[-o, o],
|
||||
[-o, -o],
|
||||
];
|
||||
const outline = dirs.map(([x, y]) => `${x}px ${y}px ${b}px ${color}`).join(",");
|
||||
return `text-shadow:${outline},0 0 ${o * 2}px ${color};`;
|
||||
@@ -147,7 +153,8 @@ function clockParts(now: Date, p: PlexPlayerPrefs): { time: string; date: string
|
||||
const yyyy = now.getFullYear();
|
||||
const mon = now.toLocaleString(loc, { month: "short" }).replace(/\./g, "").toUpperCase();
|
||||
const dow = now.toLocaleString(loc, { weekday: "short" }).replace(/\./g, "");
|
||||
const date = p.clockDateStyle === "hu" ? `${yyyy}-${mon}-${dd} ${dow}` : `${dd}-${mon}-${yyyy} ${dow}`;
|
||||
const date =
|
||||
p.clockDateStyle === "hu" ? `${yyyy}-${mon}-${dd} ${dow}` : `${dd}-${mon}-${yyyy} ${dow}`;
|
||||
return { time, date };
|
||||
}
|
||||
// hls.js plays our non-zero-start `-copyts` HLS stream ~1.0s BEHIND the media clock (measured by
|
||||
@@ -242,7 +249,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
}, [prefs.subOffset]);
|
||||
const [menuOpen, setMenuOpen] = useState(false); // gear = tuning (tabbed)
|
||||
const [tracksOpen, setTracksOpen] = useState(false); // separate quick menu = audio/subtitle tracks
|
||||
const [settingsTab, setSettingsTab] = useState<"sync" | "playback" | "subtitle" | "clock">("sync");
|
||||
const [settingsTab, setSettingsTab] = useState<"sync" | "playback" | "subtitle" | "clock">(
|
||||
"sync",
|
||||
);
|
||||
const audioRef = useRef<number | null>(null);
|
||||
const menuOpenRef = useRef(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
@@ -263,7 +272,7 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
tracksRef.current = node as HTMLDivElement | null;
|
||||
tracksFade.ref(node);
|
||||
},
|
||||
[tracksFade.ref]
|
||||
[tracksFade.ref],
|
||||
);
|
||||
// Keyboard-shortcut cheat sheet (toggled with "h") + a live wall clock for the top bar.
|
||||
const [helpOpen, setHelpOpen] = useState(false);
|
||||
@@ -313,7 +322,8 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
// hls.js plays our non-zero-start copyts stream ~1s BEHIND the media clock (see HLS_SUB_LAG);
|
||||
// fold that into the session start so the seekbar clock, seeking AND the subtitle shift are all
|
||||
// content-accurate. Direct-play (start_s 0) uses the file's real clock — no compensation.
|
||||
const trueStart = sess.mode === "hls" && sess.start_s > 0 ? sess.start_s - HLS_SUB_LAG : sess.start_s;
|
||||
const trueStart =
|
||||
sess.mode === "hls" && sess.start_s > 0 ? sess.start_s - HLS_SUB_LAG : sess.start_s;
|
||||
sessionStartRef.current = trueStart;
|
||||
setSessionK(trueStart);
|
||||
// Belt-and-suspenders resume: the play() fired on MANIFEST_PARSED / loadedmetadata can be
|
||||
@@ -466,7 +476,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
// sees the resume position when the user actually stops — not a timeline call every 10 seconds.
|
||||
const flush = (final = false) => {
|
||||
if (absRef.current > 0 && durationRef.current > 0)
|
||||
api.plexProgress(id, Math.floor(absRef.current), durationRef.current, final).catch(() => {});
|
||||
api
|
||||
.plexProgress(id, Math.floor(absRef.current), durationRef.current, final)
|
||||
.catch(() => {});
|
||||
};
|
||||
const iv = window.setInterval(flush, 10000);
|
||||
// Pausing is a settled position — push it to Plex now rather than waiting for unmount/unload.
|
||||
@@ -544,8 +556,14 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
},
|
||||
[applyVolume, patchPrefs],
|
||||
);
|
||||
const nudgeVolume = useCallback((d: number) => setVol(clamp01(prefs.volume + d), clamp01(prefs.volume + d) === 0), [prefs.volume, setVol]);
|
||||
const toggleMute = useCallback(() => setVol(prefs.volume, !prefs.muted), [prefs.volume, prefs.muted, setVol]);
|
||||
const nudgeVolume = useCallback(
|
||||
(d: number) => setVol(clamp01(prefs.volume + d), clamp01(prefs.volume + d) === 0),
|
||||
[prefs.volume, setVol],
|
||||
);
|
||||
const toggleMute = useCallback(
|
||||
() => setVol(prefs.volume, !prefs.muted),
|
||||
[prefs.volume, prefs.muted, setVol],
|
||||
);
|
||||
// Re-apply the persisted volume/mute to the <video> each time a session becomes ready (the element
|
||||
// is fresh after a load/seek-restart, and starts at its default volume otherwise).
|
||||
useEffect(() => {
|
||||
@@ -572,29 +590,30 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
return () => document.removeEventListener("fullscreenchange", onFs);
|
||||
}, []);
|
||||
|
||||
const go = useCallback(
|
||||
(otherId: string | null | undefined) => {
|
||||
if (otherId) {
|
||||
// Stop the outgoing media BEFORE switching. If the next item fails to load (e.g. no local
|
||||
// file), loadSession returns without replacing the <video> source, leaving the old episode
|
||||
// still playing — it would then fire 'ended' against the NEW id and mark the wrong item
|
||||
// watched (and scrobble it to Plex). Pausing here severs that stale-tail race.
|
||||
try {
|
||||
videoRef.current?.pause();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
setAbs(0);
|
||||
setId(otherId);
|
||||
const go = useCallback((otherId: string | null | undefined) => {
|
||||
if (otherId) {
|
||||
// Stop the outgoing media BEFORE switching. If the next item fails to load (e.g. no local
|
||||
// file), loadSession returns without replacing the <video> source, leaving the old episode
|
||||
// still playing — it would then fire 'ended' against the NEW id and mark the wrong item
|
||||
// watched (and scrobble it to Plex). Pausing here severs that stale-tail race.
|
||||
try {
|
||||
videoRef.current?.pause();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
setAbs(0);
|
||||
setId(otherId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Prev/next: from the playlist queue when playing one, else the item's own episode neighbours.
|
||||
const qIdx = queue ? queue.indexOf(id) : -1;
|
||||
const prevId = queue ? (qIdx > 0 ? queue[qIdx - 1] : null) : detail?.prev_id;
|
||||
const nextId = queue ? (qIdx >= 0 && qIdx < queue.length - 1 ? queue[qIdx + 1] : null) : detail?.next_id;
|
||||
const nextId = queue
|
||||
? qIdx >= 0 && qIdx < queue.length - 1
|
||||
? queue[qIdx + 1]
|
||||
: null
|
||||
: detail?.next_id;
|
||||
|
||||
// Download the ORIGINAL physical file (no re-encode/repackage). One user gesture, direct link.
|
||||
const download = useCallback(() => {
|
||||
@@ -613,7 +632,10 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
audioRef.current = ord;
|
||||
setAudioOrd(ord);
|
||||
// Remember the LANGUAGE (not the ord) so F5 / the next item restores this choice. Menu stays open.
|
||||
patchPrefs({ audioLang: ord == null ? "" : (detail?.audio_streams.find((s) => s.ord === ord)?.language ?? "") });
|
||||
patchPrefs({
|
||||
audioLang:
|
||||
ord == null ? "" : (detail?.audio_streams.find((s) => s.ord === ord)?.language ?? ""),
|
||||
});
|
||||
if (multiAudioRef.current && hlsRef.current) {
|
||||
// Multi-rendition: switch the audio track CLIENT-SIDE — instant, same timeline, no restart.
|
||||
try {
|
||||
@@ -632,7 +654,10 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
const changeSubtitle = useCallback(
|
||||
(ord: number | null) => {
|
||||
setSubOrd(ord);
|
||||
patchPrefs({ subLang: ord == null ? "" : (detail?.subtitle_streams.find((s) => s.ord === ord)?.language ?? "") });
|
||||
patchPrefs({
|
||||
subLang:
|
||||
ord == null ? "" : (detail?.subtitle_streams.find((s) => s.ord === ord)?.language ?? ""),
|
||||
});
|
||||
},
|
||||
[detail, patchPrefs],
|
||||
);
|
||||
@@ -677,7 +702,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
const settingsTabs = [
|
||||
{ key: "sync" as const, label: t("plex.player.sync") },
|
||||
{ key: "playback" as const, label: t("plex.player.playback") },
|
||||
...(textSubs.length > 0 ? [{ key: "subtitle" as const, label: t("plex.player.subStyle") }] : []),
|
||||
...(textSubs.length > 0
|
||||
? [{ key: "subtitle" as const, label: t("plex.player.subStyle") }]
|
||||
: []),
|
||||
{ key: "clock" as const, label: t("plex.player.clock") },
|
||||
];
|
||||
const activeTab = settingsTabs.some((x) => x.key === settingsTab) ? settingsTab : "sync";
|
||||
@@ -829,7 +856,8 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
case "Backspace":
|
||||
// HTPC-style "stop & back to the feed" — mirrors the mouse Back button (shared cascade).
|
||||
e.preventDefault();
|
||||
if (skipProgressRef.current != null) cancelAutoSkipRef.current(); // cancel auto-skip, don't go back
|
||||
if (skipProgressRef.current != null)
|
||||
cancelAutoSkipRef.current(); // cancel auto-skip, don't go back
|
||||
else handleBack();
|
||||
break;
|
||||
case "Escape":
|
||||
@@ -845,7 +873,22 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [togglePlay, toggleFs, seekTo, go, nudgeVolume, toggleMute, cycleAudio, cycleSubtitle, detail, prevId, nextId, onClose, wake, handleBack]);
|
||||
}, [
|
||||
togglePlay,
|
||||
toggleFs,
|
||||
seekTo,
|
||||
go,
|
||||
nudgeVolume,
|
||||
toggleMute,
|
||||
cycleAudio,
|
||||
cycleSubtitle,
|
||||
detail,
|
||||
prevId,
|
||||
nextId,
|
||||
onClose,
|
||||
wake,
|
||||
handleBack,
|
||||
]);
|
||||
|
||||
const activeMarker: PlexMarker | undefined = detail?.markers.find(
|
||||
(m) => abs >= m.start_s && abs < m.end_s - 1,
|
||||
@@ -919,7 +962,14 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
skipTimerRef.current = null;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeMarker?.type, activeMarker?.start_s, prefs.autoSkipIntro, prefs.autoSkipCredits, prefs.autoSkipDelay, doSkip]);
|
||||
}, [
|
||||
activeMarker?.type,
|
||||
activeMarker?.start_s,
|
||||
prefs.autoSkipIntro,
|
||||
prefs.autoSkipCredits,
|
||||
prefs.autoSkipDelay,
|
||||
doSkip,
|
||||
]);
|
||||
|
||||
// Seekbar hover: a timestamp tooltip that follows the cursor. Store the position as a FRACTION
|
||||
// (0..1), not pixels: the bar sits inside the transform:scale(1.25) subtree, so a px `left` would be
|
||||
@@ -974,7 +1024,12 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
cursor: uiVisible ? "default" : "none",
|
||||
...(fs
|
||||
? { width: "100vw", height: "100vh" }
|
||||
: { width: "80vw", height: "80vh", transform: "scale(1.25)", transformOrigin: "top left" }),
|
||||
: {
|
||||
width: "80vw",
|
||||
height: "80vh",
|
||||
transform: "scale(1.25)",
|
||||
transformOrigin: "top left",
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{/* Per-user subtitle appearance (font size / colour / background). ::cue is global, but this
|
||||
@@ -1031,7 +1086,11 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 text-white">
|
||||
<button onClick={handleBack} className="p-1.5 rounded-lg hover:bg-white/15" title={t("plex.player.back")}>
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="p-1.5 rounded-lg hover:bg-white/15"
|
||||
title={t("plex.player.back")}
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="min-w-0">
|
||||
@@ -1087,7 +1146,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
</>
|
||||
)}
|
||||
<span className="relative">
|
||||
{activeMarker.type === "intro" ? t("plex.player.skipIntro") : t("plex.player.skipCredits")}
|
||||
{activeMarker.type === "intro"
|
||||
? t("plex.player.skipIntro")
|
||||
: t("plex.player.skipCredits")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -1137,16 +1198,25 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
{fmt(hover.t)}
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-y-0 left-0 bg-white/30 rounded-full" style={{ width: `${bufPct}%` }} />
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 bg-white/30 rounded-full"
|
||||
style={{ width: `${bufPct}%` }}
|
||||
/>
|
||||
{/* intro/credit marker regions */}
|
||||
{detail?.markers.map((m, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute inset-y-0 bg-amber-400/50"
|
||||
style={{ left: `${(m.start_s / duration) * 100}%`, width: `${((m.end_s - m.start_s) / duration) * 100}%` }}
|
||||
style={{
|
||||
left: `${(m.start_s / duration) * 100}%`,
|
||||
width: `${((m.end_s - m.start_s) / duration) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<div className="absolute inset-y-0 left-0 bg-accent rounded-full" style={{ width: `${headPct}%` }} />
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 bg-accent rounded-full"
|
||||
style={{ width: `${headPct}%` }}
|
||||
/>
|
||||
<div
|
||||
className={`absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-3 h-3 rounded-full bg-accent transition-opacity ${
|
||||
scrub != null ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
||||
@@ -1178,7 +1248,11 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Ctrl label={t("plex.player.mute")} onClick={toggleMute}>
|
||||
{muted || volume === 0 ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
|
||||
{muted || volume === 0 ? (
|
||||
<VolumeX className="w-5 h-5" />
|
||||
) : (
|
||||
<Volume2 className="w-5 h-5" />
|
||||
)}
|
||||
</Ctrl>
|
||||
{/* Volume bar: plain accent fill on a neutral track (as before); hover shows the 0–100
|
||||
value under the cursor; click/drag sets it. */}
|
||||
@@ -1230,7 +1304,12 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
pushes later while paused, since the remaining time then rides the advancing clock. */}
|
||||
{duration > 0 && (
|
||||
<span className="ml-1.5 text-white/45">
|
||||
({clockParts(new Date(now.getTime() + Math.max(0, duration - abs) * 1000), prefs).time})
|
||||
(
|
||||
{
|
||||
clockParts(new Date(now.getTime() + Math.max(0, duration - abs) * 1000), prefs)
|
||||
.time
|
||||
}
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -1324,7 +1403,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
key={tab.key}
|
||||
onClick={() => setSettingsTab(tab.key)}
|
||||
className={`flex-1 whitespace-nowrap rounded px-2 py-1 text-[11px] ${
|
||||
activeTab === tab.key ? "bg-white/15 text-accent" : "text-white/70 hover:bg-white/10"
|
||||
activeTab === tab.key
|
||||
? "bg-white/15 text-accent"
|
||||
: "text-white/70 hover:bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
@@ -1427,7 +1508,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
onChange={(v) => patchPrefs({ subPos: v })}
|
||||
/>
|
||||
<div className="flex items-center justify-between px-1 py-1.5">
|
||||
<span className="text-[12px] text-white/80">{t("plex.player.subColor")}</span>
|
||||
<span className="text-[12px] text-white/80">
|
||||
{t("plex.player.subColor")}
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
value={prefs.subColor}
|
||||
@@ -1436,13 +1519,19 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-1 py-1.5">
|
||||
<span className="text-[12px] text-white/80">{t("plex.player.subBg")}</span>
|
||||
<span className="text-[12px] text-white/80">
|
||||
{t("plex.player.subBg")}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="flex items-center gap-1 text-[11px] text-white/70">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={prefs.subBg === "transparent"}
|
||||
onChange={(e) => patchPrefs({ subBg: e.target.checked ? "transparent" : "#000000" })}
|
||||
onChange={(e) =>
|
||||
patchPrefs({
|
||||
subBg: e.target.checked ? "transparent" : "#000000",
|
||||
})
|
||||
}
|
||||
className="accent-accent"
|
||||
/>
|
||||
{t("plex.player.subBgNone")}
|
||||
@@ -1468,7 +1557,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
/>
|
||||
{prefs.subShadow > 0 && (
|
||||
<div className="flex items-center justify-between px-1 py-1.5">
|
||||
<span className="text-[12px] text-white/80">{t("plex.player.subShadowColor")}</span>
|
||||
<span className="text-[12px] text-white/80">
|
||||
{t("plex.player.subShadowColor")}
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
value={prefs.subShadowColor}
|
||||
@@ -1502,7 +1593,9 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
onChange={(v) => patchPrefs({ clockSize: v })}
|
||||
/>
|
||||
<div className="flex items-center justify-between px-1 py-1.5">
|
||||
<span className="text-[12px] text-white/80">{t("plex.player.clockColor")}</span>
|
||||
<span className="text-[12px] text-white/80">
|
||||
{t("plex.player.clockColor")}
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
value={prefs.clockColor}
|
||||
@@ -1522,10 +1615,15 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
key={st}
|
||||
onClick={() => patchPrefs({ clockDateStyle: st })}
|
||||
className={`flex-1 rounded px-2 py-1 text-[11px] ${
|
||||
prefs.clockDateStyle === st ? "bg-white/15 text-accent" : "text-white/70 hover:bg-white/10"
|
||||
prefs.clockDateStyle === st
|
||||
? "bg-white/15 text-accent"
|
||||
: "text-white/70 hover:bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{clockParts(now, { ...prefs, clockDate: true, clockDateStyle: st }).date}
|
||||
{
|
||||
clockParts(now, { ...prefs, clockDate: true, clockDateStyle: st })
|
||||
.date
|
||||
}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -1635,7 +1733,7 @@ export default function PlexPlayer({ itemId, onClose, queue }: Props) {
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1648,7 +1746,11 @@ function BackClose({ onBack }: { onBack: () => void }) {
|
||||
|
||||
// --- settings-panel building blocks ------------------------------------------------------------
|
||||
function PanelHead({ children }: { children: ReactNode }) {
|
||||
return <div className="px-1 pt-2 pb-1 text-[11px] uppercase tracking-wide text-white/50">{children}</div>;
|
||||
return (
|
||||
<div className="px-1 pt-2 pb-1 text-[11px] uppercase tracking-wide text-white/50">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Slider({
|
||||
label,
|
||||
@@ -1697,11 +1799,24 @@ function Slider({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Toggle({ label, checked, onChange }: { label: string; checked: boolean; onChange: (v: boolean) => void }) {
|
||||
function Toggle({
|
||||
label,
|
||||
checked,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex cursor-pointer items-center justify-between px-1 py-1.5">
|
||||
<span className="text-[12px] text-white/80">{label}</span>
|
||||
<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} className="h-4 w-4 accent-accent" />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
className="h-4 w-4 accent-accent"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -1722,7 +1837,8 @@ function Ctrl({
|
||||
align?: "start" | "center" | "end";
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const pos = align === "start" ? "left-0" : align === "end" ? "right-0" : "left-1/2 -translate-x-1/2";
|
||||
const pos =
|
||||
align === "start" ? "left-0" : align === "end" ? "right-0" : "left-1/2 -translate-x-1/2";
|
||||
return (
|
||||
<div className="relative flex group/ctrl">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user