first commit

This commit is contained in:
Harivansh Rathi 2024-12-14 00:37:18 -05:00
commit 1cdbffff09
200 changed files with 30007 additions and 0 deletions

View file

@ -0,0 +1,18 @@
import styles from './styles.module.scss';
const BackgroundRays = () => {
return (
<div className={`${styles.rayContainer} `}>
<div className={`${styles.lightRay} ${styles.ray1}`}></div>
<div className={`${styles.lightRay} ${styles.ray2}`}></div>
<div className={`${styles.lightRay} ${styles.ray3}`}></div>
<div className={`${styles.lightRay} ${styles.ray4}`}></div>
<div className={`${styles.lightRay} ${styles.ray5}`}></div>
<div className={`${styles.lightRay} ${styles.ray6}`}></div>
<div className={`${styles.lightRay} ${styles.ray7}`}></div>
<div className={`${styles.lightRay} ${styles.ray8}`}></div>
</div>
);
};
export default BackgroundRays;

View file

@ -0,0 +1,246 @@
.rayContainer {
// Theme-specific colors
--ray-color-primary: color-mix(in srgb, var(--primary-color), transparent 30%);
--ray-color-secondary: color-mix(in srgb, var(--secondary-color), transparent 30%);
--ray-color-accent: color-mix(in srgb, var(--accent-color), transparent 30%);
// Theme-specific gradients
--ray-gradient-primary: radial-gradient(var(--ray-color-primary) 0%, transparent 70%);
--ray-gradient-secondary: radial-gradient(var(--ray-color-secondary) 0%, transparent 70%);
--ray-gradient-accent: radial-gradient(var(--ray-color-accent) 0%, transparent 70%);
position: fixed;
inset: 0;
overflow: hidden;
animation: fadeIn 1.5s ease-out;
pointer-events: none;
z-index: 0;
// background-color: transparent;
:global(html[data-theme='dark']) & {
mix-blend-mode: screen;
}
:global(html[data-theme='light']) & {
mix-blend-mode: multiply;
}
}
.lightRay {
position: absolute;
border-radius: 100%;
:global(html[data-theme='dark']) & {
mix-blend-mode: screen;
}
:global(html[data-theme='light']) & {
mix-blend-mode: multiply;
opacity: 0.4;
}
}
.ray1 {
width: 600px;
height: 800px;
background: var(--ray-gradient-primary);
transform: rotate(65deg);
top: -500px;
left: -100px;
filter: blur(80px);
opacity: 0.6;
animation: float1 15s infinite ease-in-out;
}
.ray2 {
width: 400px;
height: 600px;
background: var(--ray-gradient-secondary);
transform: rotate(-30deg);
top: -300px;
left: 200px;
filter: blur(60px);
opacity: 0.6;
animation: float2 18s infinite ease-in-out;
}
.ray3 {
width: 500px;
height: 400px;
background: var(--ray-gradient-accent);
top: -320px;
left: 500px;
filter: blur(65px);
opacity: 0.5;
animation: float3 20s infinite ease-in-out;
}
.ray4 {
width: 400px;
height: 450px;
background: var(--ray-gradient-secondary);
top: -350px;
left: 800px;
filter: blur(55px);
opacity: 0.55;
animation: float4 17s infinite ease-in-out;
}
.ray5 {
width: 350px;
height: 500px;
background: var(--ray-gradient-primary);
transform: rotate(-45deg);
top: -250px;
left: 1000px;
filter: blur(45px);
opacity: 0.6;
animation: float5 16s infinite ease-in-out;
}
.ray6 {
width: 300px;
height: 700px;
background: var(--ray-gradient-accent);
transform: rotate(75deg);
top: -400px;
left: 600px;
filter: blur(75px);
opacity: 0.45;
animation: float6 19s infinite ease-in-out;
}
.ray7 {
width: 450px;
height: 600px;
background: var(--ray-gradient-primary);
transform: rotate(45deg);
top: -450px;
left: 350px;
filter: blur(65px);
opacity: 0.55;
animation: float7 21s infinite ease-in-out;
}
.ray8 {
width: 380px;
height: 550px;
background: var(--ray-gradient-secondary);
transform: rotate(-60deg);
top: -380px;
left: 750px;
filter: blur(58px);
opacity: 0.6;
animation: float8 14s infinite ease-in-out;
}
@keyframes float1 {
0%,
100% {
transform: rotate(65deg) translate(0, 0);
}
25% {
transform: rotate(70deg) translate(30px, 20px);
}
50% {
transform: rotate(60deg) translate(-20px, 40px);
}
75% {
transform: rotate(68deg) translate(-40px, 10px);
}
}
@keyframes float2 {
0%,
100% {
transform: rotate(-30deg) scale(1);
}
33% {
transform: rotate(-25deg) scale(1.1);
}
66% {
transform: rotate(-35deg) scale(0.95);
}
}
@keyframes float3 {
0%,
100% {
transform: translate(0, 0) rotate(0deg);
}
25% {
transform: translate(40px, 20px) rotate(5deg);
}
75% {
transform: translate(-30px, 40px) rotate(-5deg);
}
}
@keyframes float4 {
0%,
100% {
transform: scale(1) rotate(0deg);
}
50% {
transform: scale(1.15) rotate(10deg);
}
}
@keyframes float5 {
0%,
100% {
transform: rotate(-45deg) translate(0, 0);
}
33% {
transform: rotate(-40deg) translate(25px, -20px);
}
66% {
transform: rotate(-50deg) translate(-25px, 20px);
}
}
@keyframes float6 {
0%,
100% {
transform: rotate(75deg) scale(1);
filter: blur(75px);
}
50% {
transform: rotate(85deg) scale(1.1);
filter: blur(65px);
}
}
@keyframes float7 {
0%,
100% {
transform: rotate(45deg) translate(0, 0);
opacity: 0.55;
}
50% {
transform: rotate(40deg) translate(-30px, 30px);
opacity: 0.65;
}
}
@keyframes float8 {
0%,
100% {
transform: rotate(-60deg) scale(1);
}
25% {
transform: rotate(-55deg) scale(1.05);
}
75% {
transform: rotate(-65deg) scale(0.95);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

View file

@ -0,0 +1,133 @@
import * as RadixDialog from '@radix-ui/react-dialog';
import { motion, type Variants } from 'framer-motion';
import React, { memo, type ReactNode } from 'react';
import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings';
import { IconButton } from './IconButton';
export { Close as DialogClose, Root as DialogRoot } from '@radix-ui/react-dialog';
const transition = {
duration: 0.15,
ease: cubicEasingFn,
};
export const dialogBackdropVariants = {
closed: {
opacity: 0,
transition,
},
open: {
opacity: 1,
transition,
},
} satisfies Variants;
export const dialogVariants = {
closed: {
x: '-50%',
y: '-40%',
scale: 0.96,
opacity: 0,
transition,
},
open: {
x: '-50%',
y: '-50%',
scale: 1,
opacity: 1,
transition,
},
} satisfies Variants;
interface DialogButtonProps {
type: 'primary' | 'secondary' | 'danger';
children: ReactNode;
onClick?: (event: React.UIEvent) => void;
}
export const DialogButton = memo(({ type, children, onClick }: DialogButtonProps) => {
return (
<button
className={classNames(
'inline-flex h-[35px] items-center justify-center rounded-lg px-4 text-sm leading-none focus:outline-none',
{
'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text hover:bg-bolt-elements-button-primary-backgroundHover':
type === 'primary',
'bg-bolt-elements-button-secondary-background text-bolt-elements-button-secondary-text hover:bg-bolt-elements-button-secondary-backgroundHover':
type === 'secondary',
'bg-bolt-elements-button-danger-background text-bolt-elements-button-danger-text hover:bg-bolt-elements-button-danger-backgroundHover':
type === 'danger',
},
)}
onClick={onClick}
>
{children}
</button>
);
});
export const DialogTitle = memo(({ className, children, ...props }: RadixDialog.DialogTitleProps) => {
return (
<RadixDialog.Title
className={classNames(
'px-5 py-4 flex items-center justify-between border-b border-bolt-elements-borderColor text-lg font-semibold leading-6 text-bolt-elements-textPrimary',
className,
)}
{...props}
>
{children}
</RadixDialog.Title>
);
});
export const DialogDescription = memo(({ className, children, ...props }: RadixDialog.DialogDescriptionProps) => {
return (
<RadixDialog.Description
className={classNames('px-5 py-4 text-bolt-elements-textPrimary text-md', className)}
{...props}
>
{children}
</RadixDialog.Description>
);
});
interface DialogProps {
children: ReactNode | ReactNode[];
className?: string;
onBackdrop?: (event: React.UIEvent) => void;
onClose?: (event: React.UIEvent) => void;
}
export const Dialog = memo(({ className, children, onBackdrop, onClose }: DialogProps) => {
return (
<RadixDialog.Portal>
<RadixDialog.Overlay onClick={onBackdrop} asChild>
<motion.div
className="bg-black/50 fixed inset-0 z-max"
initial="closed"
animate="open"
exit="closed"
variants={dialogBackdropVariants}
/>
</RadixDialog.Overlay>
<RadixDialog.Content asChild>
<motion.div
className={classNames(
'fixed top-[50%] left-[50%] z-max max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-2 shadow-lg focus:outline-none overflow-hidden',
className,
)}
initial="closed"
animate="open"
exit="closed"
variants={dialogVariants}
>
{children}
<RadixDialog.Close asChild onClick={onClose}>
<IconButton icon="i-ph:x" className="absolute top-[10px] right-[10px]" />
</RadixDialog.Close>
</motion.div>
</RadixDialog.Content>
</RadixDialog.Portal>
);
});

View file

@ -0,0 +1,77 @@
import { memo } from 'react';
import { classNames } from '~/utils/classNames';
type IconSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
interface BaseIconButtonProps {
size?: IconSize;
className?: string;
iconClassName?: string;
disabledClassName?: string;
title?: string;
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
type IconButtonWithoutChildrenProps = {
icon: string;
children?: undefined;
} & BaseIconButtonProps;
type IconButtonWithChildrenProps = {
icon?: undefined;
children: string | JSX.Element | JSX.Element[];
} & BaseIconButtonProps;
type IconButtonProps = IconButtonWithoutChildrenProps | IconButtonWithChildrenProps;
export const IconButton = memo(
({
icon,
size = 'xl',
className,
iconClassName,
disabledClassName,
disabled = false,
title,
onClick,
children,
}: IconButtonProps) => {
return (
<button
className={classNames(
'flex items-center text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
{
[classNames('opacity-30', disabledClassName)]: disabled,
},
className,
)}
title={title}
disabled={disabled}
onClick={(event) => {
if (disabled) {
return;
}
onClick?.(event);
}}
>
{children ? children : <div className={classNames(icon, getIconSize(size), iconClassName)}></div>}
</button>
);
},
);
function getIconSize(size: IconSize) {
if (size === 'sm') {
return 'text-sm';
} else if (size === 'md') {
return 'text-md';
} else if (size === 'lg') {
return 'text-lg';
} else if (size === 'xl') {
return 'text-xl';
} else {
return 'text-2xl';
}
}

View file

@ -0,0 +1,27 @@
import { memo, useEffect, useState } from 'react';
interface LoadingDotsProps {
text: string;
}
export const LoadingDots = memo(({ text }: LoadingDotsProps) => {
const [dotCount, setDotCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setDotCount((prevDotCount) => (prevDotCount + 1) % 4);
}, 500);
return () => clearInterval(interval);
}, []);
return (
<div className="flex justify-center items-center h-full">
<div className="relative">
<span>{text}</span>
<span className="absolute left-[calc(100%-12px)]">{'.'.repeat(dotCount)}</span>
<span className="invisible">...</span>
</div>
</div>
);
});

View file

@ -0,0 +1,20 @@
import { memo } from 'react';
import { classNames } from '~/utils/classNames';
interface PanelHeaderProps {
className?: string;
children: React.ReactNode;
}
export const PanelHeader = memo(({ className, children }: PanelHeaderProps) => {
return (
<div
className={classNames(
'flex items-center gap-2 bg-bolt-elements-background-depth-2 text-bolt-elements-textSecondary border-b border-bolt-elements-borderColor px-4 py-1 min-h-[34px] text-sm',
className,
)}
>
{children}
</div>
);
});

View file

@ -0,0 +1,36 @@
import { memo } from 'react';
import { classNames } from '~/utils/classNames';
interface PanelHeaderButtonProps {
className?: string;
disabledClassName?: string;
disabled?: boolean;
children: string | JSX.Element | Array<JSX.Element | string>;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
export const PanelHeaderButton = memo(
({ className, disabledClassName, disabled = false, children, onClick }: PanelHeaderButtonProps) => {
return (
<button
className={classNames(
'flex items-center shrink-0 gap-1.5 px-1.5 rounded-md py-0.5 text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed',
{
[classNames('opacity-30', disabledClassName)]: disabled,
},
className,
)}
disabled={disabled}
onClick={(event) => {
if (disabled) {
return;
}
onClick?.(event);
}}
>
{children}
</button>
);
},
);

View file

@ -0,0 +1,17 @@
import { memo } from 'react';
import { IconButton } from '~/components/ui/IconButton';
interface SettingsButtonProps {
onClick: () => void;
}
export const SettingsButton = memo(({ onClick }: SettingsButtonProps) => {
return (
<IconButton
onClick={onClick}
icon="i-ph:gear"
size="xl"
title="Settings"
className="text-[#666] hover:text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive/10 transition-colors"
/>
);
});

View file

@ -0,0 +1,65 @@
import { motion } from 'framer-motion';
import { memo } from 'react';
import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings';
import { genericMemo } from '~/utils/react';
interface SliderOption<T> {
value: T;
text: string;
}
export interface SliderOptions<T> {
left: SliderOption<T>;
right: SliderOption<T>;
}
interface SliderProps<T> {
selected: T;
options: SliderOptions<T>;
setSelected?: (selected: T) => void;
}
export const Slider = genericMemo(<T,>({ selected, options, setSelected }: SliderProps<T>) => {
const isLeftSelected = selected === options.left.value;
return (
<div className="flex items-center flex-wrap shrink-0 gap-1 bg-bolt-elements-background-depth-1 overflow-hidden rounded-full p-1">
<SliderButton selected={isLeftSelected} setSelected={() => setSelected?.(options.left.value)}>
{options.left.text}
</SliderButton>
<SliderButton selected={!isLeftSelected} setSelected={() => setSelected?.(options.right.value)}>
{options.right.text}
</SliderButton>
</div>
);
});
interface SliderButtonProps {
selected: boolean;
children: string | JSX.Element | Array<JSX.Element | string>;
setSelected: () => void;
}
const SliderButton = memo(({ selected, children, setSelected }: SliderButtonProps) => {
return (
<button
onClick={setSelected}
className={classNames(
'bg-transparent text-sm px-2.5 py-0.5 rounded-full relative',
selected
? 'text-bolt-elements-item-contentAccent'
: 'text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive',
)}
>
<span className="relative z-10">{children}</span>
{selected && (
<motion.span
layoutId="pill-tab"
transition={{ duration: 0.2, ease: cubicEasingFn }}
className="absolute inset-0 z-0 bg-bolt-elements-item-backgroundAccent rounded-full"
></motion.span>
)}
</button>
);
});

View file

@ -0,0 +1,37 @@
import { memo } from 'react';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { classNames } from '~/utils/classNames';
interface SwitchProps {
className?: string;
checked?: boolean;
onCheckedChange?: (event: boolean) => void;
}
export const Switch = memo(({ className, onCheckedChange, checked }: SwitchProps) => {
return (
<SwitchPrimitive.Root
className={classNames(
'relative h-6 w-11 cursor-pointer rounded-full bg-bolt-elements-button-primary-background',
'transition-colors duration-200 ease-in-out',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
'data-[state=checked]:bg-bolt-elements-item-contentAccent',
className,
)}
checked={checked}
onCheckedChange={(e) => onCheckedChange?.(e)}
>
<SwitchPrimitive.Thumb
className={classNames(
'block h-5 w-5 rounded-full bg-white',
'shadow-lg shadow-black/20',
'transition-transform duration-200 ease-in-out',
'translate-x-0.5',
'data-[state=checked]:translate-x-[1.375rem]',
'will-change-transform',
)}
/>
</SwitchPrimitive.Root>
);
});

View file

@ -0,0 +1,29 @@
import { useStore } from '@nanostores/react';
import { memo, useEffect, useState } from 'react';
import { themeStore, toggleTheme } from '~/lib/stores/theme';
import { IconButton } from './IconButton';
interface ThemeSwitchProps {
className?: string;
}
export const ThemeSwitch = memo(({ className }: ThemeSwitchProps) => {
const theme = useStore(themeStore);
const [domLoaded, setDomLoaded] = useState(false);
useEffect(() => {
setDomLoaded(true);
}, []);
return (
domLoaded && (
<IconButton
className={className}
icon={theme === 'dark' ? 'i-ph-sun-dim-duotone' : 'i-ph-moon-stars-duotone'}
size="xl"
title="Toggle Theme"
onClick={toggleTheme}
/>
)
);
});

View file

@ -0,0 +1,73 @@
import * as Tooltip from '@radix-ui/react-tooltip';
interface TooltipProps {
tooltip: React.ReactNode;
children: React.ReactNode;
sideOffset?: number;
className?: string;
arrowClassName?: string;
tooltipStyle?: React.CSSProperties;
position?: 'top' | 'bottom' | 'left' | 'right';
maxWidth?: number;
delay?: number;
}
const WithTooltip = ({
tooltip,
children,
sideOffset = 5,
className = '',
arrowClassName = '',
tooltipStyle = {},
position = 'top',
maxWidth = 250,
delay = 0,
}: TooltipProps) => {
return (
<Tooltip.Root delayDuration={delay}>
<Tooltip.Trigger asChild>{children}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side={position}
className={`
z-[2000]
px-2.5
py-1.5
max-h-[300px]
select-none
rounded-md
bg-bolt-elements-background-depth-3
text-bolt-elements-textPrimary
text-sm
leading-tight
shadow-lg
animate-in
fade-in-0
zoom-in-95
data-[state=closed]:animate-out
data-[state=closed]:fade-out-0
data-[state=closed]:zoom-out-95
${className}
`}
sideOffset={sideOffset}
style={{
maxWidth,
...tooltipStyle,
}}
>
<div className="break-words">{tooltip}</div>
<Tooltip.Arrow
className={`
fill-bolt-elements-background-depth-3
${arrowClassName}
`}
width={12}
height={6}
/>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
);
};
export default WithTooltip;