This commit is contained in:
Виталий Лавшонок
2025-10-22 20:51:12 +03:00
parent 0c6c1417c6
commit 4d0cafdce8
29 changed files with 5601 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import React from "react";
import { cn } from "../../lib/cn";
interface ButtonProps {
disabled?: boolean;
text?: string;
className?: string;
onClick: () => void;
}
export const PrimaryButton: React.FC<ButtonProps> = ({
disabled = false,
text = "",
className,
onClick,
}) => {
return (
<label
className={cn(
"grid relative cursor-pointer select-none group w-fit box-border",
disabled && "pointer-events-none",
className
)}
onClick={() => onClick()}
>
{/* Основной контейнер, */}
<div
className={cn(
"group-active:scale-90 flex items-center justify-center box-border z-10 relative transition-all duration-300",
"rounded-[10px]",
"group-hover:bg-liquid-lighter group-hover:ring-[1px] group-hover:ring-liquid-darkmain group-hover:ring-inset",
"px-[16px] py-[8px]",
"bg-liquid-darkmain",
disabled && "bg-liquid-lighter"
)}
>
{/* Скрытый button */}
<button
className={cn(
"absolute opacity-0 -z-10 h-0 w-0",
"[&:focus-visible+*]:outline-liquid-brightmain",
)}
disabled={disabled}
onClick={() => {}}
/>
{/* Граница при выделении через tab */}
<div
className={cn(
"absolute outline-offset-[2.5px] border-[2px] border-transparent outline-[2.5px] outline outline-transparent transition-all duration-300 text-transparent box-border text-[18px] font-bold p-0 ,m-0 leading-[23px]",
"rounded-[10px]",
"px-[16px] py-[8px]",
)}
>
{text}
</div>
<div
className={cn(
"transition-all duration-300 text-liquid-white text-[18px] font-bold p-0 m-0 leading-[23px]",
"group-hover:text-liquid-brightmain ",
disabled && "text-liquid-light"
)}
>
{text}
</div>
</div>
</label>
);
};

View File

@@ -0,0 +1,67 @@
import React from "react";
import { cn } from "../../lib/cn";
interface ButtonProps {
disabled?: boolean;
text?: string;
className?: string;
onClick: () => void;
}
export const SecondaryButton: React.FC<ButtonProps> = ({
disabled = false,
text = "",
className,
onClick,
}) => {
return (
<label
className={cn(
"grid relative cursor-pointer select-none group w-fit box-border",
disabled && "pointer-events-none",
className
)}
onClick={() => onClick()}
>
{/* Основной контейнер, */}
<div
className={cn(
"group-active:scale-90 flex items-center justify-center box-border z-10 relative transition-all duration-300",
"rounded-[10px]",
"group-hover:bg-liquid-background",
"px-[16px] py-[8px]",
"bg-liquid-lighter"
)}
>
{/* Скрытый button */}
<button
className={cn(
"absolute opacity-0 -z-10 h-0 w-0",
"[&:focus-visible+*]:outline-liquid-brightmain",
)}
disabled={disabled}
onClick={() => {}}
/>
{/* Граница при выделении через tab */}
<div
className={cn(
"absolute outline-offset-[2.5px] border-[2px] border-transparent outline-[2.5px] outline outline-transparent transition-all duration-300 text-transparent box-border text-[18px] font-bold p-0 ,m-0 leading-[23px]",
"rounded-[10px]",
"px-[16px] py-[8px]",
)}
>
{text}
</div>
<div
className={cn(
"transition-all duration-300 text-liquid-white text-[18px] font-bold p-0 m-0 leading-[23px]",
disabled && "text-liquid-light"
)}
>
{text}
</div>
</div>
</label>
);
};

View File

@@ -0,0 +1,157 @@
import React from "react";
import { cn } from "../../lib/cn";
import { motion } from "framer-motion";
const pathVariants = {
hidden: {
opacity: 0,
pathLength: 0,
},
visible: {
opacity: 1,
pathLength: 1,
transition: {
delay: 0.15,
duration: 0.4,
ease: "easeInOut",
},
},
};
const sizeVariants = {
sm: "h-4 w-4",
md: "h-5 w-5",
lg: "h-6 w-6",
};
const colorsVariants = {
default: "bg-default",
primary: "bg-liquid-brightmain",
secondary: "bg-liquid-darkmain",
success: "bg-liquid-green",
warning: "bg-liquid-orange",
danger: "bg-liquid-red",
};
const focuseOutlineVariants = {
default: "[&:focus-visible+*]:outline-default",
primary: "[&:focus-visible+*]:outline-liquid-brightmain",
secondary: "[&:focus-visible+*]:outline-liquid-darkmain",
success: "[&:focus-visible+*]:outline-liquid-green",
warning: "[&:focus-visible+*]:outline-liquid-orange",
danger: "[&:focus-visible+*]:outline-liquid-red",
};
const radiusVraiants = {
none: "",
sm: "rounded-[3.5px]",
md: "rounded-[5px]",
lg: "rounded-[7px]",
full: "rounded-full",
};
interface CheckboxProps {
size?: "sm" | "md" | "lg";
radius?: "none" | "sm" | "md" | "lg" | "full";
disabled?: boolean;
color?:
| "default"
| "primary"
| "secondary"
| "success"
| "warning"
| "danger";
label?: string;
variant?: "default" | "label";
className?: string;
defaultState?: boolean;
onChange: (state: boolean) => void;
}
export const Checkbox: React.FC<CheckboxProps> = ({
size = "md",
radius = "md",
disabled = false,
color = "primary",
label = "",
variant = "label",
className,
onChange,
defaultState = false,
}) => {
const [active, setActive] = React.useState<boolean>(defaultState);
React.useEffect(() => onChange(active), [active]);
return (
<motion.label
className={cn(
variant == "label" && "grid-cols-[auto_1fr] items-center gap-2",
"grid relative cursor-pointer p-2 select-none group ",
className,
disabled && "pointer-events-none opacity-50",
variant == "default" && ""
)}
>
<div
className={cn(
"group-hover:bg-default-100 group-active:scale-90 flex items-center justify-center bg-transparent hover:bg-default-100 box-border border-solid border-[2px] border-default z-10 relative transition-all duration-300",
sizeVariants[size],
radiusVraiants[radius]
)}
>
<input
className={cn(
"absolute opacity-0 -z-10 h-0 w-0",
focuseOutlineVariants[color]
)}
disabled={disabled}
type="checkbox"
onChange={() => {
setActive(!active);
}}
/>
<div
className={cn(
"absolute outline-offset-[2.5px] outline-[2.5px] outline outline-transparent transition-all duration-200",
sizeVariants[size],
radiusVraiants[radius]
)}
></div>
<span
className={cn(
"absolute transition-all duration-300",
sizeVariants[size],
colorsVariants[color],
radiusVraiants[radius],
active && "opacity-100 scale-100",
!active && "opacity-0 scale-0"
)}
>
<svg
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{active && (
<motion.path
strokeWidth="1.5"
d="M5 8.22L7.66571 10.44L11.22 6"
stroke="white"
strokeLinecap="round"
variants={pathVariants}
initial="hidden"
animate="visible"
/>
)}
</svg>
</span>
</div>
{variant == "label" && (
<div className="select-none text-layout-foeground transition-all duration-200">
{label}
</div>
)}
</motion.label>
);
};

View File

@@ -0,0 +1,142 @@
import React from "react";
import { cn } from "../../lib/cn";
/* Варианты для скользящего шарика */
const inputVariants = {
size: {
sm: "h-[3rem] w-[27rem]",
md: "h-[3.5rem] w-[27rem]",
lg: "h-[4rem] w-[27rem]",
},
colors: {
default: "text-default-600 bg-default-200 placeholder-default-600",
primary: "text-liquid-brightmain bg-liquid-brightmain placeholder-liquid-brightmain",
secondary: "text-secondary bg-secondary-100 placeholder-secondary",
success: "text-success bg-success-100 placeholder-success",
warning: "text-warning bg-warning-100 placeholder-warning",
danger: "text-danger bg-danger-100 placeholder-danger",
},
radius: {
none: "rounded-none",
sm: "rounded-[8px]",
md: "rounded-[12px]",
lg: "rounded-[16px]",
full: "rounded-full",
},
colorsHovered: {
default: "focus:bg-default-200 hover:bg-default-300",
primary: "focus:bg-liquid-brightmain hover:bg-liquid-brightmain",
secondary: "focus:bg-secondary-100 hover:bg-secondary-200",
success: "focus:bg-success-100 hover:bg-success-200",
warning: "focus:bg-warning-100 hover:bg-warning-200",
danger: "focus:bg-danger-100 hover:bg-danger-200",
},
label: {
default: "text-default-600",
primary: "text-liquid-brightmain",
secondary: "text-secondary",
success: "text-success",
warning: "text-warning",
danger: "text-dange",
},
};
interface SwitchProps {
size?: "sm" | "md" | "lg";
radius?: "sm" | "md" | "lg" | "none" | "full";
disabled?: boolean;
placeholder?: string;
type?: "text" | "email" | "password" | "first_name";
color?:
| "default"
| "primary"
| "secondary"
| "success"
| "warning"
| "danger";
id?: string;
required?: boolean;
label?: string;
labelType?: "none" | "in" | "out" | "left" | "in-fixed" | "out-fixed";
variant?: "flat" | "faded" | "bordered" | "underlined";
className?: string;
defaultState?: string;
onChange: (state: string) => void;
}
export const Input: React.FC<SwitchProps> = ({
size = "sm",
// radius = "md",
type = "text",
id = "",
disabled = false,
required = false,
// color = "default",
label = "",
labelType = "in",
placeholder = "",
variant = "flat",
className,
onChange,
defaultState = "",
}) => {
const [value, setValue] = React.useState<string>(defaultState);
React.useEffect(() => onChange(value), [value]);
if (labelType == "in" || labelType == "in-fixed")
return (
<label
className={cn(
"grid relative select-none group cursor-text overflow-hidden",
disabled && "pointer-events-none opacity-50",
className
)}
>
{/* Основной контейнер, */}
<div
className={cn(
"box-border z-10 relative transition-all duration-300 h-fit flex items-center px-3 rounded-[12px] overflow-hidden ",
inputVariants.size[size],
variant == "flat" &&
"group-hover:bg-default-300 group-focus-within:bg-default-300 bg-default-200",
variant == "faded" &&
"group-hover:bg-default-300 group-focus-within:bg-default-300 bg-default-200 border-[2px] border-default-300 group-focus-within:border-default group-hover:border-default",
variant == "bordered" &&
"border-[2px] border-default-300 group-focus-within:border-default group-hover:border-default"
)}
>
<input
id={type == "password" ? type : id}
type={type}
className={cn(
"outline outline-transparent transition-all duration-300 bg-transparent absolute left-0 bottom-0 placeholder-default-500 text-layout-foreground w-full px-3 pt-[10px] h-full",
placeholder == "" && value == ""
? "opacity-0 focus:opacity-100"
: " [&:-webkit-autofill+*]:text-white"
)}
value={value}
placeholder={placeholder}
disabled={disabled}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<div
className={cn(
"absolute text-default-600 transition-all duration-300 my-[2px]",
placeholder == "" && value == "" && labelType != "in-fixed"
? "top-[20%] text-[16px] group-focus-within:top-0 group-focus-within:text-[12px] group-focus-within:text-default-700"
: "top-0 text-[12px] text-default-700"
)}
>
{label}
{required && <span className="text-danger m-[2px]">*</span>}
</div>
</div>
</label>
);
return <></>;
};

View File

@@ -0,0 +1,187 @@
import React from "react";
import { cn } from "../../lib/cn";
/* Варианты размера контейнера */
const sizeVariants = {
sm: "h-6 w-10",
md: "h-7 w-12",
lg: "h-8 w-14",
};
/* Варианты для скользящего шарика */
const switchVariants = {
size: {
sm: "h-4 w-4",
md: "h-5 w-5",
lg: "h-6 w-6",
},
activeSize: {
sm: "group-active:w-5",
md: "group-active:w-6",
lg: "group-active:w-7",
},
iconSize: {
sm: "h-3 w-3",
md: "h-[0.875rem] w-[0.875rem]",
lg: "h-4 w-4",
},
};
const colorsVariants = {
default: "bg-default",
primary: "bg-liquid-brightmain",
secondary: "bg-liquid-darkmain",
success: "bg-liquid-green",
warning: "bg-liquid-orange",
danger: "bg-liquid-red",
};
const focuseOutlineVariants = {
default: "[&:focus-visible+*]:outline-default",
primary: "[&:focus-visible+*]:outline-liquid-brightmain",
secondary: "[&:focus-visible+*]:outline-liquid-darkmain",
success: "[&:focus-visible+*]:outline-liquid-green",
warning: "[&:focus-visible+*]:outline-liquid-orange",
danger: "[&:focus-visible+*]:outline-liquid-red",
};
/**
* Иконка солнца
*/
const sun = (
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 9.5C7.933 9.5 9.5 7.933 9.5 6C9.5 4.067 7.933 2.5 6 2.5C4.067 2.5 2.5 4.067 2.5 6C2.5 7.933 4.067 9.5 6 9.5Z"
fill="#292D32"
/>
<path
d="M6 11.48C5.725 11.48 5.5 11.275 5.5 11V10.96C5.5 10.685 5.725 10.46 6 10.46C6.275 10.46 6.5 10.685 6.5 10.96C6.5 11.235 6.275 11.48 6 11.48ZM9.57 10.07C9.44 10.07 9.315 10.02 9.215 9.925L9.15 9.86C8.955 9.665 8.955 9.35 9.15 9.155C9.345 8.96 9.66 8.96 9.855 9.155L9.92 9.22C10.115 9.415 10.115 9.73 9.92 9.925C9.825 10.02 9.7 10.07 9.57 10.07ZM2.43 10.07C2.3 10.07 2.175 10.02 2.075 9.925C1.88 9.73 1.88 9.415 2.075 9.22L2.14 9.155C2.335 8.96 2.65 8.96 2.845 9.155C3.04 9.35 3.04 9.665 2.845 9.86L2.78 9.925C2.685 10.02 2.555 10.07 2.43 10.07ZM11 6.5H10.96C10.685 6.5 10.46 6.275 10.46 6C10.46 5.725 10.685 5.5 10.96 5.5C11.235 5.5 11.48 5.725 11.48 6C11.48 6.275 11.275 6.5 11 6.5ZM1.04 6.5H1C0.725 6.5 0.5 6.275 0.5 6C0.5 5.725 0.725 5.5 1 5.5C1.275 5.5 1.52 5.725 1.52 6C1.52 6.275 1.315 6.5 1.04 6.5ZM9.505 2.995C9.375 2.995 9.25 2.945 9.15 2.85C8.955 2.655 8.955 2.34 9.15 2.145L9.215 2.08C9.41 1.885 9.725 1.885 9.92 2.08C10.115 2.275 10.115 2.59 9.92 2.785L9.855 2.85C9.76 2.945 9.635 2.995 9.505 2.995ZM2.495 2.995C2.365 2.995 2.24 2.945 2.14 2.85L2.075 2.78C1.88 2.585 1.88 2.27 2.075 2.075C2.27 1.88 2.585 1.88 2.78 2.075L2.845 2.14C3.04 2.335 3.04 2.65 2.845 2.845C2.75 2.945 2.62 2.995 2.495 2.995ZM6 1.52C5.725 1.52 5.5 1.315 5.5 1.04V1C5.5 0.725 5.725 0.5 6 0.5C6.275 0.5 6.5 0.725 6.5 1C6.5 1.275 6.275 1.52 6 1.52Z"
fill="#292D32"
/>
</svg>
);
/**
* Иконка луны
*/
const moon = (
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.765 7.965C10.685 7.83 10.46 7.62 9.89999 7.72C9.58999 7.775 9.27499 7.8 8.95999 7.785C7.79499 7.735 6.73999 7.2 6.00499 6.375C5.35499 5.65 4.95499 4.705 4.94999 3.685C4.94999 3.115 5.05999 2.565 5.28499 2.045C5.50499 1.54 5.34999 1.275 5.23999 1.165C5.12499 1.05 4.85499 0.890001 4.32499 1.11C2.27999 1.97 1.01499 4.02 1.16499 6.215C1.31499 8.28 2.76499 10.045 4.68499 10.71C5.14499 10.87 5.62999 10.965 6.12999 10.985C6.20999 10.99 6.28999 10.995 6.36999 10.995C8.04499 10.995 9.61499 10.205 10.605 8.86C10.94 8.395 10.85 8.1 10.765 7.965Z"
fill="#292D32"
/>
</svg>
);
interface SwitchProps {
size?: "sm" | "md" | "lg";
disabled?: boolean;
color?:
| "default"
| "primary"
| "secondary"
| "success"
| "warning"
| "danger";
label?: string;
variant?: "default" | "label" | "icon" | "theme";
className?: string;
defaultState?: boolean;
onChange: (state: boolean) => void;
}
export const Switch: React.FC<SwitchProps> = ({
size = "sm",
disabled = false,
color = "primary",
label = "",
variant = "default",
className,
onChange,
defaultState = false,
}) => {
const [active, setActive] = React.useState<boolean>(defaultState);
React.useEffect(() => onChange(active), [active]);
return (
<label
className={cn(
variant == "label" && "grid-cols-[auto_1fr] items-center gap-2",
"grid relative cursor-pointer p-2 select-none group",
disabled && "pointer-events-none opacity-50",
className
)}
>
{/* Основной контейнер, */}
<div
className={cn(
" flex items-center justify-center box-border z-10 relative transition-all duration-300 rounded-full",
sizeVariants[size],
active ? colorsVariants[color] : "bg-default-200"
)}
>
{/* Скрытый checkbox */}
<input
className={cn(
"absolute opacity-0 -z-10 h-0 w-0",
focuseOutlineVariants[color]
)}
disabled={disabled}
type="checkbox"
onChange={() => {
setActive(!active);
}}
/>
<div
className={cn(
"absolute outline-offset-[2.5px] outline-[2.5px] outline outline-transparent transition-all duration-300 rounded-full",
sizeVariants[size]
)}
></div>
{/* Шарик */}
<span
className={cn(
"bg-white rounded-full absolute transition-all duration-300 m-1 flex items-center justify-center",
switchVariants.size[size],
switchVariants.activeSize[size],
active
? "right-[0%]"
: "right-[calc(50%-0.25rem)] group-active:right-[calc(50%-0.5rem)]"
)}
>
{variant == "theme" && (
<>
<div
className={cn(
"absolute transition-all duration-300",
switchVariants.iconSize[size],
active ? "opacity-100 scale-100" : "opacity-0 scale-50"
)}
>
{moon}
</div>
<div
className={cn(
"absolute transition-all duration-300",
switchVariants.iconSize[size],
active ? "opacity-0 scale-50" : "opacity-100 scale-100"
)}
>
{sun}
</div>
</>
)}
</span>
</div>
{variant == "label" && (
<div className="select-none text-layout-foreground transition-all duration-200">
{label}
</div>
)}
</label>
);
};