feat: share post to other apps
This commit is contained in:
@@ -235,11 +235,23 @@ type TriggerRenderer = (state: {
|
||||
* the popover off the bottom of the viewport. */
|
||||
type Placement = "below-right" | "above-left";
|
||||
|
||||
/** How the controls are presented.
|
||||
*
|
||||
* - "popover" (default) — the icon trigger + floating panel pattern
|
||||
* used in SiteHeader/sidebar. Centred triggers on narrow viewports
|
||||
* can push the panel off-screen, which is why auth users get the
|
||||
* dedicated settings page instead.
|
||||
* - "inline" — render the controls flat as part of normal page flow,
|
||||
* full-width up to a sensible cap. Used by /u/me/settings. */
|
||||
type Variant = "popover" | "inline";
|
||||
|
||||
export function ReadingSettings({
|
||||
variant = "popover",
|
||||
renderTrigger,
|
||||
placement = "below-right",
|
||||
onOpenChange,
|
||||
}: {
|
||||
variant?: Variant;
|
||||
renderTrigger?: TriggerRenderer;
|
||||
placement?: Placement;
|
||||
/** Fires every time the popover opens or closes. Used by the sidebar
|
||||
@@ -456,6 +468,9 @@ export function ReadingSettings({
|
||||
visiblePalettes.find((p) => p.id === activePaletteId)?.name ?? "";
|
||||
|
||||
if (!mounted) {
|
||||
// Inline variant: render nothing until mounted so we don't flash a
|
||||
// misaligned skeleton on the dedicated settings page.
|
||||
if (variant === "inline") return null;
|
||||
return (
|
||||
<Button variant="ghost" size="icon-sm" aria-label="Reading settings">
|
||||
<Settings className="size-4" />
|
||||
@@ -463,53 +478,9 @@ export function ReadingSettings({
|
||||
);
|
||||
}
|
||||
|
||||
const toggle = () => setOpen((v) => !v);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{renderTrigger ? (
|
||||
renderTrigger({ open, toggle, buttonRef })
|
||||
) : (
|
||||
<button
|
||||
ref={buttonRef as React.Ref<HTMLButtonElement>}
|
||||
aria-label="Reading settings"
|
||||
aria-expanded={open}
|
||||
onClick={toggle}
|
||||
className={cn(
|
||||
"size-9 inline-flex items-center justify-center rounded-lg",
|
||||
"text-muted-foreground hover:text-foreground hover:bg-muted",
|
||||
"transition-colors outline-none",
|
||||
"focus-visible:ring-2 focus-visible:ring-ring",
|
||||
)}
|
||||
>
|
||||
<Settings className="size-5 md:size-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{open && (
|
||||
<MaybePortal portal={placement === "above-left" && portalPos !== null}>
|
||||
<div
|
||||
ref={panelRef}
|
||||
style={
|
||||
placement === "above-left" && portalPos
|
||||
? {
|
||||
position: "fixed",
|
||||
left: portalPos.left,
|
||||
bottom: portalPos.bottom,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={cn(
|
||||
"w-72 rounded-lg border border-border bg-card shadow-lg p-4",
|
||||
"max-h-[80vh] overflow-y-auto",
|
||||
placement === "above-left" && portalPos
|
||||
? "z-[100]"
|
||||
: placement === "above-left"
|
||||
? "absolute bottom-full left-0 mb-2 z-50"
|
||||
: "absolute top-full right-0 mt-2 z-50",
|
||||
)}
|
||||
>
|
||||
{/* Theme selector */}
|
||||
const panelInner = (
|
||||
<>
|
||||
{/* Theme selector */}
|
||||
<div className="mb-4">
|
||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Theme
|
||||
@@ -635,7 +606,70 @@ export function ReadingSettings({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// ── Inline variant ──────────────────────────────────────────────────
|
||||
// Renders the same controls as a flush, full-width card. No popover
|
||||
// positioning, so it can't drift off-viewport on a narrow screen. Used
|
||||
// by /u/me/settings.
|
||||
if (variant === "inline") {
|
||||
return (
|
||||
<div className="w-full max-w-xl rounded-lg border border-border bg-card p-4 sm:p-5">
|
||||
{panelInner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Popover variant (default) ───────────────────────────────────────
|
||||
const toggle = () => setOpen((v) => !v);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{renderTrigger ? (
|
||||
renderTrigger({ open, toggle, buttonRef })
|
||||
) : (
|
||||
<button
|
||||
ref={buttonRef as React.Ref<HTMLButtonElement>}
|
||||
aria-label="Reading settings"
|
||||
aria-expanded={open}
|
||||
onClick={toggle}
|
||||
className={cn(
|
||||
"size-9 inline-flex items-center justify-center rounded-lg",
|
||||
"text-muted-foreground hover:text-foreground hover:bg-muted",
|
||||
"transition-colors outline-none",
|
||||
"focus-visible:ring-2 focus-visible:ring-ring",
|
||||
)}
|
||||
>
|
||||
<Settings className="size-5 md:size-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{open && (
|
||||
<MaybePortal portal={placement === "above-left" && portalPos !== null}>
|
||||
<div
|
||||
ref={panelRef}
|
||||
style={
|
||||
placement === "above-left" && portalPos
|
||||
? {
|
||||
position: "fixed",
|
||||
left: portalPos.left,
|
||||
bottom: portalPos.bottom,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={cn(
|
||||
"w-72 rounded-lg border border-border bg-card shadow-lg p-4",
|
||||
"max-h-[80vh] overflow-y-auto",
|
||||
placement === "above-left" && portalPos
|
||||
? "z-[100]"
|
||||
: placement === "above-left"
|
||||
? "absolute bottom-full left-0 mb-2 z-50"
|
||||
: "absolute top-full right-0 mt-2 z-50",
|
||||
)}
|
||||
>
|
||||
{panelInner}
|
||||
</div>
|
||||
</MaybePortal>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user