Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions client/src/components/modules/menu-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ export interface ArcadeMenuItemProps
active?: boolean;
className?: string;
onClick?: () => void;
disabled?: boolean;
}

export const ArcadeMenuItem = React.forwardRef<
HTMLButtonElement,
ArcadeMenuItemProps
>(
(
{ Icon, value, label, active, className, variant, size, onClick, ...props },
{ Icon, value, label, active, className, variant, size, onClick, disabled, ...props },
ref,
) => {
const handleFocus = useCallback((e: React.FocusEvent<HTMLDivElement>) => {
Expand All @@ -49,17 +50,20 @@ export const ArcadeMenuItem = React.forwardRef<
value={value}
className={cn(
"w-full p-0 flex cursor-pointer select-none transition-colors data-[state=active]:bg-transparent data-[state=active]:shadow-none",
disabled && "opacity-50 cursor-not-allowed",
className,
)}
ref={ref}
disabled={disabled}
{...props}
>
<SelectItem
onSelect={onClick}
onSelect={disabled ? undefined : onClick}
onFocus={handleFocus}
data-active={active}
value={value}
simplified
disabled={disabled}
className={cn(arcadeMenuItemVariants({ variant, size }))}
>
<div className={cn("flex justify-start items-center gap-1")}>
Expand All @@ -72,4 +76,5 @@ export const ArcadeMenuItem = React.forwardRef<
},
);

// Updated to support disabled state - TypeScript refresh
export default ArcadeMenuItem;
8 changes: 6 additions & 2 deletions client/src/components/modules/tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,26 @@ export interface ArcadeTabProps extends VariantProps<typeof arcadeTabVariants> {
active?: boolean;
className?: string;
onClick?: () => void;
disabled?: boolean;
suffix?: string;
}

export const ArcadeTab = React.forwardRef<HTMLButtonElement, ArcadeTabProps>(
(
{ Icon, value, label, active, className, variant, size, onClick, ...props },
{ Icon, value, label, active, className, variant, size, onClick, disabled, ...props },
ref,
) => {
return (
<TabsTrigger
value={value}
className={cn(
"p-0 flex flex-col items-center cursor-pointer select-none transition-colors data-[state=active]:bg-transparent data-[state=active]:shadow-none data-[state=active]:drop-shadow-none",
disabled && "opacity-50 cursor-not-allowed",
className,
)}
onClick={onClick}
onClick={disabled ? undefined : onClick}
ref={ref}
disabled={disabled}
{...props}
>
<div
Expand Down
101 changes: 74 additions & 27 deletions client/src/components/modules/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ export interface ArcadeTabsProps
VariantProps<typeof arcadeTabsVariants> {
defaultValue?: TabValue;
order?: TabValue[];
disabledTabs?: TabValue[];
onTabClick?: (tab: TabValue) => void;
}

export const ArcadeTabs = ({
defaultValue = "activity",
defaultValue,
order = [
"activity",
"leaderboard",
Expand All @@ -82,13 +83,32 @@ export const ArcadeTabs = ({
"items",
"holders",
],
disabledTabs = ["activity"],
onTabClick,
variant,
size,
className,
children,
}: ArcadeTabsProps) => {
const [active, setActive] = useState<TabValue>(defaultValue);
const { isMobile, isPWA } = useDevice();

// Reorder tabs to put disabled ones at the end
const orderedTabs = useMemo(() => {
const enabledTabs = order.filter(tab => !disabledTabs.includes(tab));
const disabledTabsInOrder = order.filter(tab => disabledTabs.includes(tab));
return [...enabledTabs, ...disabledTabsInOrder];
}, [order, disabledTabs]);

// Set proper default value - use first enabled tab if default is disabled or not provided
const effectiveDefaultValue = useMemo(() => {
if (defaultValue && !disabledTabs.includes(defaultValue)) {
return defaultValue;
}
const firstEnabledTab = orderedTabs.find(tab => !disabledTabs.includes(tab));
return firstEnabledTab || orderedTabs[0];
}, [defaultValue, disabledTabs, orderedTabs]);

const [active, setActive] = useState<TabValue>(effectiveDefaultValue);
const [visibleTabs, setVisibleTabs] = useState<TabValue[]>(order);
const [overflowTabs, setOverflowTabs] = useState<TabValue[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
Expand All @@ -97,22 +117,30 @@ export const ArcadeTabs = ({
new Map<TabValue, { width: number; visible: boolean }>(),
);

const { isMobile, isPWA } = useDevice();

useEffect(() => {
if (isMobile) return;
if (!hiddenRef.current) return;
const tabWidths = new Map<TabValue, { width: number; visible: boolean }>();
hiddenRef.current.childNodes.forEach((node) => {
const element = node as HTMLDivElement;
const tab = element.textContent?.toLowerCase();
if (tab) {
const visible = order.includes(tab as TabValue);
tabWidths.set(tab as TabValue, { width: element.offsetWidth, visible });
const textContent = element.textContent?.toLowerCase();
if (textContent) {
// Extract the base tab name by removing "(coming soon)" suffix if present
const tab = textContent.replace(/\s*\(coming soon\)\s*$/, '') as TabValue;
// All tabs in orderedTabs should be visible
const visible = orderedTabs.includes(tab);
tabWidths.set(tab, { width: element.offsetWidth, visible });
}
});

// Ensure all tabs in orderedTabs have entries in tabWidths
orderedTabs.forEach((tab) => {
if (!tabWidths.has(tab)) {
tabWidths.set(tab, { width: 100, visible: true }); // Default width for missing tabs
}
});
tabRefs.current = tabWidths;
}, [tabRefs, hiddenRef, order, isMobile]);
}, [tabRefs, hiddenRef, orderedTabs, isMobile]);

useEffect(() => {
if (isMobile) return;
Expand All @@ -126,12 +154,13 @@ export const ArcadeTabs = ({
const newVisibleTabs: TabValue[] = [];
const newOverflowTabs: TabValue[] = [];

order.forEach((tab) => {
orderedTabs.forEach((tab) => {
const { width, visible } = tabRefs.current.get(tab) || {
width: 0,
visible: false,
};
if (!visible) return;

if (
usedWidth + width <= availableWidth &&
newOverflowTabs.length === 0
Expand All @@ -154,18 +183,17 @@ export const ArcadeTabs = ({
observer.observe(containerRef.current!);
return () => observer.disconnect();
}, [
order,
containerRef.current,
orderedTabs,
visibleTabs,
overflowTabs,
tabRefs,
isMobile,
]);

useEffect(() => {
if (order.includes(active)) return;
setActive(defaultValue);
}, [order, active, defaultValue]);
if (orderedTabs.includes(active)) return;
setActive(effectiveDefaultValue);
}, [orderedTabs, active, effectiveDefaultValue]);

const overflowActive = useMemo(
() => overflowTabs.includes(active),
Expand All @@ -174,7 +202,7 @@ export const ArcadeTabs = ({

return (
<Tabs
defaultValue={defaultValue}
defaultValue={effectiveDefaultValue}
value={active}
onValueChange={(value: string) => setActive(value as TabValue)}
className="h-full flex flex-col overflow-hidden"
Expand All @@ -187,14 +215,15 @@ export const ArcadeTabs = ({
)}
>
<TabsList className="h-full w-full p-0 flex gap-2 items-start justify-around">
{order.map((tab) => (
{orderedTabs.map((tab) => (
<Tab
key={tab}
tab={tab}
value={active}
size={size}
onTabClick={() => onTabClick?.(tab as TabValue)}
isMobile={true}
disabled={tab === "activity" ? disabledTabs.includes(tab) : false}
/>
))}
</TabsList>
Expand All @@ -209,8 +238,8 @@ export const ArcadeTabs = ({
)}
>
<div ref={hiddenRef} className="flex gap-2 absolute invisible">
{order.map((tab) => (
<Tab key={tab} tab={tab} value={active} size={size} />
{orderedTabs.map((tab) => (
<Tab key={tab} tab={tab} value={active} size={size} disabled={tab === "activity" ? disabledTabs.includes(tab) : false} />
))}
</div>
{visibleTabs.map((tab) => (
Expand All @@ -220,6 +249,7 @@ export const ArcadeTabs = ({
value={active}
size={size}
onTabClick={() => onTabClick?.(tab as TabValue)}
disabled={tab === "activity" ? disabledTabs.includes(tab) : false}
/>
))}
<Select>
Expand All @@ -238,6 +268,7 @@ export const ArcadeTabs = ({
size={size}
onTabClick={() => onTabClick?.(tab as TabValue)}
item={true}
disabled={tab === "activity" ? disabledTabs.includes(tab) : false}
/>
))}
</SelectContent>
Expand All @@ -256,13 +287,15 @@ const Tab = ({
onTabClick,
item,
isMobile = false,
disabled = false,
}: {
tab: TabValue;
value: string;
size: "default" | null | undefined;
onTabClick?: () => void;
item?: boolean;
isMobile?: boolean;
disabled?: boolean;
}) => {
const props = {
value: tab,
Expand All @@ -271,6 +304,7 @@ const Tab = ({
onClick: onTabClick,
item,
isMobile,
disabled,
};
switch (tab) {
case "inventory":
Expand Down Expand Up @@ -309,6 +343,7 @@ interface NavButtonProps {
onClick?: () => void;
item?: boolean;
isMobile?: boolean;
disabled?: boolean;
}

const InventoryNavButton = React.forwardRef<HTMLButtonElement, NavButtonProps>(
Expand Down Expand Up @@ -490,16 +525,27 @@ const GuildsNavButton = React.forwardRef<HTMLButtonElement, NavButtonProps>(
);

const ActivityNavButton = React.forwardRef<HTMLButtonElement, NavButtonProps>(
({ value, active, size, onClick, item, isMobile }, ref) => {
({ value, active, size, onClick, item, isMobile, disabled }, ref) => {
if (isMobile) {
return (
<TabsTrigger
className="p-0 grow data-[state=active]:bg-background-transparent data-[state=active]:shadow-none"
className={cn(
"p-0 grow data-[state=active]:bg-background-transparent data-[state=active]:shadow-none",
disabled && "opacity-50 cursor-not-allowed"
)}
value={value}
ref={ref}
disabled={disabled}
>
<BottomTab status={active ? "active" : null} onClick={onClick}>
<PulseIcon variant="solid" size="lg" />
<BottomTab status={active ? "active" : null} onClick={disabled ? undefined : onClick}>
<div className="flex flex-col items-center gap-1">
<PulseIcon variant="solid" size="lg" />
{disabled && (
<div className="text-xs text-gray-500 whitespace-nowrap">
Coming soon
</div>
)}
</div>
</BottomTab>
</TabsTrigger>
);
Expand All @@ -511,10 +557,10 @@ const ActivityNavButton = React.forwardRef<HTMLButtonElement, NavButtonProps>(
ref={ref}
value={value}
Icon={<PulseIcon variant="solid" size="sm" />}
label="Activity"
label={disabled ? "Activity (Coming soon)" : "Activity"}
active={active}
size={size}
onClick={onClick}
onClick={disabled ? undefined : onClick}
/>
);
}
Expand All @@ -524,10 +570,11 @@ const ActivityNavButton = React.forwardRef<HTMLButtonElement, NavButtonProps>(
ref={ref}
value={value}
Icon={<PulseIcon variant="solid" size="sm" />}
label="Activity"
label={disabled ? "Activity (Coming soon)" : "Activity"}
active={active}
size={size}
onClick={onClick}
onClick={disabled ? undefined : onClick}
disabled={disabled}
/>
);
},
Expand Down