Skip to content

Instantly share code, notes, and snippets.

@almirbi
Created October 30, 2024 19:56
'use client';
export type Flag = keyof BetaFlagsValueProps;
export type Value = boolean | undefined;
export type BetaFlagsContextProps = {
toggleBetaFlag: (flag: Flag) => void;
setBetaFlag: (flag: Flag, value: boolean) => void;
magicToggle: () => void;
getBetaFlag: (flag: Flag) => boolean;
betaFlags: BetaFlagsValueProps;
};
export type BetaFlagsValueProps = {
v2: Value;
};
import { createContext, useContext } from 'react';
export const BetaFlagsContext = createContext({} as BetaFlagsContextProps);
export const useBetaFlagsContext = () => {
const context = useContext(BetaFlagsContext);
if (!context)
throw new Error(
'useBetaFlagsContext must be use inside BetaFlagsContext.Provider'
);
return context;
};
'use client';
import { useEffect, useState } from 'react';
import {
BetaFlagsContext,
BetaFlagsValueProps,
Flag,
} from './BetaFlagsContext';
import { Switcher } from './Switcher';
export type BetaFlagsProviderProps = {
children: React.ReactNode;
defaultValues?: BetaFlagsValueProps;
};
declare global {
interface Window {
secretSauce?: {
toggleBetaFlag?: (flag: Flag) => void;
magicToggle?: () => void;
};
}
}
const isProduction = process.env.NODE_ENV === 'production';
const defaults: BetaFlagsValueProps = isProduction
? ['v2'].reduce(
(prev, current) => ({ ...prev, [current]: false }),
{} as BetaFlagsValueProps
)
: {
v2: process.env.NEXT_PUBLIC_V2 === 'enabled',
};
export function BetaFlagsProvider({ children }: BetaFlagsProviderProps) {
const [betaFlags, setBetaFlags] = useState(defaults);
const [switcherOn, setSwitcherOn] = useState(!isProduction);
const toggleBetaFlag = (flag: Flag) => {
setBetaFlags(current => ({ ...current, [flag]: !current[flag] }));
};
const setBetaFlag = (flag: Flag, value: boolean) => {
setBetaFlags(current => ({ ...current, [flag]: value }));
};
const getBetaFlag = (flag: Flag) => {
return !!betaFlags[flag];
};
const magicToggle = () => setSwitcherOn(current => !current);
useEffect(() => {
if (typeof window !== 'undefined') {
window.secretSauce = {
toggleBetaFlag,
magicToggle,
};
if (!isProduction) {
setSwitcherOn(true);
}
}
}, []);
return (
<BetaFlagsContext.Provider
value={{
toggleBetaFlag,
getBetaFlag,
betaFlags,
magicToggle,
setBetaFlag,
}}
>
{switcherOn && <Switcher />}
{children}
</BetaFlagsContext.Provider>
);
}
import { Close, DragIndicator } from '@mui/icons-material';
import {
Box,
Card,
CardContent,
Collapse,
Fab,
IconButton,
Stack,
Zoom,
} from '@mui/material';
import { MouseEventHandler, useRef } from 'react';
type Props = {
children: React.ReactNode;
icon: React.ReactNode;
isOpen?: boolean;
onOpen?: () => void;
onClose?: () => void;
};
export const ExpandableFab = ({
children,
icon,
isOpen,
onOpen,
onClose,
}: Props) => {
const pointer = useRef({ x: 0, y: 0 });
const onMouseUp: (
val: 'open' | 'close'
) => MouseEventHandler<HTMLButtonElement> = val => e => {
const { x, y } = pointer.current;
if (Math.abs(e.clientX - x) < 3 && Math.abs(e.clientY - y) < 3) {
switch (val) {
case 'open':
onOpen?.();
break;
case 'close':
onClose?.();
break;
}
pointer.current = { x: 0, y: 0 };
}
};
const onMouseDown: MouseEventHandler<HTMLButtonElement> = e => {
pointer.current = { x: e.clientX, y: e.clientY };
};
return (
<>
<Zoom in={!isOpen}>
<Box
position={'absolute'}
sx={{ pointerEvents: isOpen ? 'none' : 'all' }}
>
<Fab onMouseDown={onMouseDown} onMouseUp={onMouseUp('open')}>
{icon}
</Fab>
</Box>
</Zoom>
<Collapse in={isOpen}>
<Card>
<Stack pt={1} px={1} justifyContent="space-between" direction="row">
<IconButton
size="small"
onMouseDown={onMouseDown}
onMouseUp={onMouseUp('close')}
>
<Close />
</IconButton>
<IconButton sx={{ cursor: 'move' }} disableRipple>
<DragIndicator />
</IconButton>
</Stack>
<CardContent>{children}</CardContent>
</Card>
</Collapse>
</>
);
};
import { Box, useTheme } from '@mui/material';
import { useEffect, useRef, useState } from 'react';
import Draggable from 'react-draggable';
type Props = { children: React.ReactNode };
export const PersistedDraggableWindow = ({ children }: Props) => {
const theme = useTheme();
const [initPosition, setInitPosition] = useState<{ x: string; y: string }>();
useEffect(() => {
if (typeof localStorage !== 'undefined') {
const switcherPos = localStorage.getItem('switcherPos');
const { x, y } = switcherPos
? JSON.parse(switcherPos)
: { y: '20px', x: 'calc(100vw - 360px)' };
setInitPosition({ x, y });
}
}, []);
const nodeRef = useRef(null);
if (!initPosition) {
return null;
}
return (
<Draggable
nodeRef={nodeRef}
cancel={'[class*="MuiDialogContent-root"]'}
onStop={(e, data) => {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(
'switcherPos',
JSON.stringify({
x: data.node.getBoundingClientRect().x,
y: data.node.getBoundingClientRect().y,
})
);
}
}}
>
<Box
position="fixed"
zIndex={theme.zIndex.appBar + 10}
top={initPosition.y}
left={initPosition.x}
ref={nodeRef}
>
{children}
</Box>
</Draggable>
);
};
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
import { Chip, FormControlLabel, Stack, Switch } from '@mui/material';
import { useState } from 'react';
import { Flag, useBetaFlagsContext } from './BetaFlagsContext';
import { ExpandableFab } from './ExpandableFab';
import { PersistedDraggableWindow } from './PersistedDraggableWindow';
export const Switcher = () => {
const { betaFlags, setBetaFlag } = useBetaFlagsContext();
const [isOpen, setIsOpen] = useState<boolean | undefined>(undefined);
return (
<PersistedDraggableWindow>
<ExpandableFab
isOpen={isOpen}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
icon={<ToggleOnIcon />}
>
<Stack>
{Object.entries(betaFlags).map(([flag, value]) => (
<FormControlLabel
key={flag}
control={
<Switch
onChange={e => {
setBetaFlag(flag as Flag, e.target.checked);
}}
checked={!!value}
/>
}
label={<Chip label={flag} />}
/>
))}
</Stack>
</ExpandableFab>
</PersistedDraggableWindow>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment