themes lol
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Avatar as MantineAvatar,
|
||||
AvatarProps as MantineAvatarProps,
|
||||
Paper,
|
||||
Box,
|
||||
Modal,
|
||||
Image,
|
||||
Group,
|
||||
@@ -22,10 +22,17 @@ interface AvatarProps
|
||||
contain?: boolean;
|
||||
}
|
||||
|
||||
// Mantine radius tokens ("sm", "xl") are valid as props but not as raw CSS
|
||||
// values — resolve them before using in style/borderRadius.
|
||||
const resolveRadius = (radius: string | number) =>
|
||||
typeof radius === "string" && /^[a-z]+$/.test(radius)
|
||||
? `var(--mantine-radius-${radius})`
|
||||
: radius;
|
||||
|
||||
const Avatar = ({
|
||||
name,
|
||||
size = 35,
|
||||
radius = "100%",
|
||||
radius = "var(--flxn-avatar-radius, 50%)",
|
||||
withBorder = true,
|
||||
disableFullscreen = false,
|
||||
contain = false,
|
||||
@@ -34,6 +41,7 @@ const Avatar = ({
|
||||
const { t } = useLingui();
|
||||
const [isFullscreenOpen, setIsFullscreenOpen] = useState(false);
|
||||
const hasImage = Boolean(props.src);
|
||||
const frameRadius = resolveRadius(radius);
|
||||
|
||||
const handleAvatarClick = () => {
|
||||
if (hasImage && !disableFullscreen) {
|
||||
@@ -43,11 +51,20 @@ const Avatar = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
p={size / 20}
|
||||
radius={radius}
|
||||
withBorder={withBorder}
|
||||
<Box
|
||||
className="flxn-avatar-frame"
|
||||
p={withBorder ? size / 20 : 0}
|
||||
style={{
|
||||
// Plain Box, not Paper: the frame must own the avatar's single
|
||||
// border (via --flxn-avatar-border) and stay immune to generic
|
||||
// theme Paper restyles; shape comes from --flxn-avatar-radius.
|
||||
borderRadius: frameRadius,
|
||||
border: withBorder
|
||||
? 'var(--flxn-avatar-border, 1px solid var(--mantine-color-default-border))'
|
||||
: undefined,
|
||||
backgroundColor: withBorder
|
||||
? 'var(--mantine-color-body)'
|
||||
: undefined,
|
||||
cursor: hasImage && !disableFullscreen ? 'pointer' : 'default',
|
||||
transition: 'transform 0.15s ease',
|
||||
}}
|
||||
@@ -69,14 +86,17 @@ const Avatar = ({
|
||||
size={size}
|
||||
radius={radius}
|
||||
w={size}
|
||||
{...props}
|
||||
styles={{
|
||||
root: {
|
||||
borderRadius: frameRadius,
|
||||
},
|
||||
image: {
|
||||
objectFit: contain ? 'contain' : 'cover',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<Modal
|
||||
opened={isFullscreenOpen}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Box, Container, Title, useComputedColorScheme } from "@mantine/core";
|
||||
import { PropsWithChildren, useEffect, useRef } from "react";
|
||||
import { Drawer as VaulDrawer } from "vaul";
|
||||
import { CHROME_COLORS, setThemeColorMeta } from "@/lib/mantine/theme-colors";
|
||||
import {
|
||||
pushDimmedThemeColor,
|
||||
popDimmedThemeColor,
|
||||
} from "@/lib/mantine/theme-colors";
|
||||
import { getTheme } from "@/themes/registry";
|
||||
import { resolveChromeColors } from "@/themes/chrome";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface DrawerProps extends PropsWithChildren {
|
||||
@@ -20,22 +26,24 @@ const Drawer: React.FC<DrawerProps> = ({
|
||||
onExited,
|
||||
}) => {
|
||||
const colorScheme = useComputedColorScheme("light");
|
||||
const { metadata } = useAuth();
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const themeId = metadata.theme;
|
||||
|
||||
useEffect(() => {
|
||||
if (!opened) return;
|
||||
|
||||
const appElement = document.querySelector<HTMLElement>(".app");
|
||||
const colors = CHROME_COLORS[colorScheme];
|
||||
const colors = resolveChromeColors(getTheme(themeId))[colorScheme];
|
||||
|
||||
appElement?.classList.add("drawer-scaling");
|
||||
setThemeColorMeta(colors.dimmed);
|
||||
pushDimmedThemeColor(colors.dimmed);
|
||||
|
||||
return () => {
|
||||
appElement?.classList.remove("drawer-scaling");
|
||||
setThemeColorMeta(colors.base);
|
||||
popDimmedThemeColor(colors.base);
|
||||
};
|
||||
}, [opened, colorScheme]);
|
||||
}, [opened, colorScheme, themeId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!opened || !contentRef.current) return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.drawerOverlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
background-color: var(--flxn-overlay);
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ function SwipeableTabs({
|
||||
}, [search?.tab, tabs, defaultTab]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
|
||||
const [stuck, setStuck] = useState(false);
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||
const controlsRefs = useRef<Record<number, HTMLSpanElement | null>>({});
|
||||
const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
|
||||
@@ -132,6 +134,20 @@ function SwipeableTabs({
|
||||
};
|
||||
}, [activeTab, updateHeight]);
|
||||
|
||||
// A 1px sentinel above the sticky strip: once it scrolls out of view the
|
||||
// strip is pinned — flip data-stuck so themes can swap flat → docked chrome.
|
||||
useEffect(() => {
|
||||
const sentinel = sentinelRef.current;
|
||||
if (!sentinel) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => setStuck(!entry.isIntersecting),
|
||||
{ threshold: 0 }
|
||||
);
|
||||
observer.observe(sentinel);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const setControlRef = useCallback(
|
||||
(index: number) => (node: HTMLSpanElement | null) => {
|
||||
controlsRefs.current[index] = node;
|
||||
@@ -148,8 +164,11 @@ function SwipeableTabs({
|
||||
|
||||
return (
|
||||
<Box style={{ touchAction: "pan-y" }}>
|
||||
<div ref={sentinelRef} aria-hidden style={{ height: 1, marginTop: -1 }} />
|
||||
<Box
|
||||
ref={setRootRef}
|
||||
className="flxn-tabs"
|
||||
data-stuck={stuck || undefined}
|
||||
pos="sticky"
|
||||
top={0}
|
||||
style={{
|
||||
@@ -157,45 +176,28 @@ function SwipeableTabs({
|
||||
paddingInline: "var(--mantine-spacing-md)",
|
||||
marginBottom: mb !== undefined ? mb : "var(--mantine-spacing-md)",
|
||||
zIndex: 100,
|
||||
backgroundColor: "var(--mantine-color-body)",
|
||||
}}
|
||||
>
|
||||
<FloatingIndicator
|
||||
target={controlsRefs.current[activeTab]}
|
||||
parent={rootRef}
|
||||
styles={{
|
||||
root: {
|
||||
borderBottom: "2px solid var(--mantine-primary-color-filled)",
|
||||
paddingInline: "0.5rem",
|
||||
},
|
||||
}}
|
||||
className="flxn-tabs-indicator"
|
||||
/>
|
||||
{tabs.map((tab, index) => (
|
||||
<UnstyledButton
|
||||
key={`${tab.label}-${index}`}
|
||||
onClick={() => changeTab(index)}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-xs)",
|
||||
textAlign: "center",
|
||||
color:
|
||||
activeTab === index
|
||||
? "var(--mantine-primary-color-filled)"
|
||||
: "var(--mantine-color-text)",
|
||||
fontWeight: activeTab === index ? 600 : 400,
|
||||
transition: "color 200ms ease, font-weight 200ms ease",
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
borderRadius: 0,
|
||||
}}
|
||||
className="flxn-tab"
|
||||
data-active={activeTab === index || undefined}
|
||||
>
|
||||
<Text
|
||||
size="sm"
|
||||
component="span"
|
||||
style={{
|
||||
// the FloatingIndicator sizes to this span — keep padding symmetric
|
||||
display: "inline-block",
|
||||
paddingInline: "0.5rem",
|
||||
paddingBottom: "0.25rem",
|
||||
paddingInline: "0.875rem",
|
||||
paddingBlock: "0.375rem",
|
||||
}}
|
||||
ref={setControlRef(index)}
|
||||
>
|
||||
|
||||
@@ -62,7 +62,7 @@ const TeamAvatar = ({
|
||||
top: -crownSize * 1.1,
|
||||
left: "45%",
|
||||
transform: "translateX(-50%)",
|
||||
color: "gold",
|
||||
color: "var(--flxn-gold)",
|
||||
rotate: "-5deg",
|
||||
}}
|
||||
>
|
||||
@@ -83,7 +83,7 @@ const TeamAvatar = ({
|
||||
top: -crownSize * 0.95,
|
||||
left: "65%",
|
||||
transform: "translateX(-50%)",
|
||||
color: "gold",
|
||||
color: "var(--flxn-gold)",
|
||||
rotate: "10deg",
|
||||
}}
|
||||
>
|
||||
@@ -116,7 +116,7 @@ const TeamAvatar = ({
|
||||
top: -crownSize * 0.6,
|
||||
left: -crownSize * 0.25,
|
||||
transform: "rotate(-25deg)",
|
||||
color: "gold",
|
||||
color: "var(--flxn-gold)",
|
||||
}}
|
||||
>
|
||||
<CrownIcon size={crownSize} weight="fill" />
|
||||
|
||||
Reference in New Issue
Block a user