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
210 changes: 143 additions & 67 deletions src/app/(bluerpint-list)/FilterAndSortButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Image from 'next/image';
import { forwardRef, useState, useRef, useEffect } from 'react';
import { Status } from '@zk-email/sdk';
import { useAuthStore } from '@/lib/stores/useAuthStore';
import { motion, AnimatePresence } from 'framer-motion';

type FilterAndSortButtonProps = ButtonProps & {};

Expand Down Expand Up @@ -69,15 +70,87 @@ const FilterAndSortButton = forwardRef<HTMLButtonElement, FilterAndSortButtonPro
replace(`${pathname}?${params.toString()}`);
};

// Animation variants for the dropdown
const dropdownVariants = {
hidden: {
opacity: 0,
scaleY: 0,
transformOrigin: "top center",
y: -10,
},
visible: {
opacity: 1,
scaleY: 1,
transformOrigin: "top center",
y: 0,
transition: {
duration: 0.05,
staggerChildren: 0.01,
delayChildren: 0.01,
},
},
exit: {
opacity: 0,
scaleY: 0,
transformOrigin: "top center",
y: -10,
transition: {
duration: 0.05,
staggerChildren: 0.01,
staggerDirection: -1,
},
},
};

// Animation variants for individual items
const itemVariants = {
hidden: {
opacity: 0,
y: -10,
scale: 0.95,
},
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.15,
},
},
exit: {
opacity: 0,
y: -10,
scale: 0.95,
transition: {
duration: 0.1,
},
},
};

// Filter items for authenticated users
const filterItems = username ? [
{ title: "Draft", value: Status.Draft },
{ title: "Compiled", value: Status.Done },
{ title: "In Progress", value: Status.InProgress },
{ title: "Failed", value: Status.Failed },
] : [];

// Sort items
const sortItems = [
{ title: "Most Stars", value: "stars" },
{ title: "Last Updated", value: "updatedAt" },
];

return (
<div ref={buttonRef} className="relative">
<button
<motion.button
className={cn(
buttonVariants({ variant: 'secondary', size: 'sm', className: 'bg-white' }),
'flex flex-col',
expanded ? 'rounded-b-none border-b-0' : ''
'flex flex-col rounded-lg'
)}
onClick={() => setExpanded(!expanded)}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.1 }}
>
<div className="flex w-max items-center gap-2">
<Image
Expand All @@ -92,73 +165,76 @@ const FilterAndSortButton = forwardRef<HTMLButtonElement, FilterAndSortButtonPro
/>
Filter and Sort
</div>
</button>
</motion.button>

{expanded && (
<div className="absolute right-[-1px] top-full z-10 box-content w-full rounded-b-md border border-t-0 border-grey-500 bg-white pb-2">
{username && (
<>
<div className="flex flex-col gap-2 px-3">
<Checkbox
title="Draft"
checked={filters.includes(Status.Draft)}
onCheckedChange={(checked: boolean) => {
handleFilter(Status.Draft, checked);
}}
/>
<Checkbox
title="Compiled"
checked={filters.includes(Status.Done)}
onCheckedChange={(checked: boolean) => {
handleFilter(Status.Done, checked);
}}
/>
<Checkbox
title="In Progress"
checked={filters.includes(Status.InProgress)}
onCheckedChange={(checked: boolean) => {
handleFilter(Status.InProgress, checked);
<AnimatePresence>
{expanded && (
<motion.div
className="absolute right-[-1px] top-[38px] z-10 box-content w-full rounded-lg border border-grey-500 bg-white py-1 overflow-hidden shadow-md"
variants={dropdownVariants}
initial="hidden"
animate="visible"
exit="exit"
>
{username && (
<>
<motion.div
className="flex w-max items-center gap-2 px-2 pt-1 pb-2"
variants={itemVariants}
>
<Image src="/assets/Faders.svg" alt="filter" width={16} height={16} />
Filter
</motion.div>

<motion.div className="flex flex-col gap-2 px-1" variants={itemVariants}>
{filterItems.map((item, index) => (
<motion.div key={item.value} variants={itemVariants}>
<Checkbox
title={item.title}
checked={filters.includes(item.value)}
onCheckedChange={(checked: boolean) => {
handleFilter(item.value, checked);
}}
/>
</motion.div>
))}
</motion.div>

<motion.div
className="mx-3 my-2"
style={{
height: '1px',
backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23E2E2E2' stroke-width='3' stroke-dasharray='6%2c 14' stroke-dashoffset='2' stroke-linecap='square'/%3e%3c/svg%3e")`,
}}
variants={itemVariants}
/>
<Checkbox
title="Failed"
checked={filters.includes(Status.Failed)}
onCheckedChange={(checked: boolean) => {
handleFilter(Status.Failed, checked);
}}
/>
</div>
<div
className="mx-3 my-2"
style={{
height: '1px',
backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23E2E2E2' stroke-width='3' stroke-dasharray='6%2c 14' stroke-dashoffset='2' stroke-linecap='square'/%3e%3c/svg%3e")`,
}}
/>
<div className="flex w-max items-center gap-2 px-3 pb-2">
<Image src="/assets/Sort.svg" alt="filter" width={16} height={16} />
Sort
</div>
</>
)}
<div className="flex flex-col gap-2 px-3">
<Checkbox
title="Most Stars"
checked={sort === 'stars'}
onCheckedChange={(checked: boolean) => {
handleSort('stars', checked);
}}
/>
<Checkbox
title="Last Updated"
checked={sort === 'updatedAt'}
onCheckedChange={(checked: boolean) => {
handleSort('updatedAt', checked);
}}
/>
</div>
</div>
)}

<motion.div
className="flex w-max items-center gap-2 px-2 pb-2"
variants={itemVariants}
>
<Image src="/assets/Sort.svg" alt="sort" width={16} height={16} />
Sort
</motion.div>
</>
)}

<motion.div className="flex flex-col gap-2 px-1" variants={itemVariants}>
{sortItems.map((item, index) => (
<motion.div key={item.value} variants={itemVariants}>
<Checkbox
title={item.title}
checked={sort === item.value}
onCheckedChange={(checked: boolean) => {
handleSort(item.value, checked);
}}
/>
</motion.div>
))}
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/app/[id]/versions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const VersionsPage = ({ params }: { params: Promise<{ id: string }> }) => {
>
{versions.length} Version{versions.length > 1 && 's'}
</Button>
<Link href={`/create`}>
{/* <Link href={`/create`}>
<Button
variant="default"
size="sm"
Expand All @@ -147,7 +147,7 @@ const VersionsPage = ({ params }: { params: Promise<{ id: string }> }) => {
>
Start fresh
</Button>
</Link>
</Link> */}
</div>
</div>
</div>
Expand Down
27 changes: 15 additions & 12 deletions src/app/components/BlueprintCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ const BlueprintCard = ({ blueprint, setStarred, setUnStarred, starred }: Bluepri
height: 'auto',
}}
/>
<span className="hidden md:inline">
{(stars || 0) < 2 ? 'Star' : 'Stars'} |{' '}
</span>
{stars ?? 0}
</button>
</div>
Expand Down Expand Up @@ -147,15 +144,21 @@ const BlueprintCard = ({ blueprint, setStarred, setUnStarred, starred }: Bluepri
<p className="mb-3 overflow-hidden text-ellipsis whitespace-nowrap">{blueprint.props.description}</p>
<div className="mt-2 flex flex-col items-start justify-between md:flex-row md:items-end">
<div className="flex flex-wrap items-center gap-2">
<p>Values extracted:</p>
{blueprint.props.decomposedRegexes?.map((dr, index) => (
<div
key={index}
className="h-fit rounded-md border border-[#D4D4D4] bg-neutral-200 px-2 py-[1px] text-[12px] leading-[16px]"
>
{dr.name} {dr.isHashed ? '(hashed)' : ''}
</div>
))}
{blueprint.props.decomposedRegexes && blueprint.props.decomposedRegexes.length > 0 ? (
<>
<p>Values extracted:</p>
{blueprint.props.decomposedRegexes.map((dr, index) => (
<div
key={index}
className="h-fit rounded-md border border-[#D4D4D4] bg-neutral-200 px-2 py-[1px] text-[12px] leading-[16px]"
>
{dr.name} {dr.isHashed ? '(hashed)' : ''}
</div>
))}
</>
) : (
<p>Values extracted: None</p>
)}
</div>
<div className="mt-2 flex w-full flex-row items-center justify-between gap-2 md:mt-0 md:w-auto">
<span
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/BlueprintTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ export const BlueprintTitle = ({

<div className="flex flex-col items-start justify-between gap-4 border-t border-grey-200 pt-4 md:flex-row">
<div className="flex flex-row items-center gap-3">
<span className="text-lg font-bold leading-6 text-grey-900 underline">
{blueprint.props.version}
<span className="text-lg font-bold leading-6 text-grey-900">
v {blueprint.props.version}
</span>
<span
className={`flex flex-row items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium ${getStatusColorLight(
Expand Down
54 changes: 35 additions & 19 deletions src/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,44 @@ const Checkbox = React.forwardRef<
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
helpText?: string;
}
>(({ className, title, helpText, ...props }, ref) => (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>(({ className, title, helpText, ...props }, ref) => {
const internalRef = React.useRef<HTMLButtonElement>(null);

// Expose the checkbox element through the forwarded ref
React.useImperativeHandle(ref, () => internalRef.current as HTMLButtonElement, []);

const handleDivClick = () => {
if (internalRef.current) {
internalRef.current.click();
}
};

return (
<div className="flex flex-col gap-2">
<div
className="flex items-center gap-2 hover:bg-accent rounded-md px-2 py-1 transition-colors cursor-pointer"
onClick={handleDivClick}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
<CheckboxPrimitive.Root
ref={internalRef}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{title ? <Label className="text-sm text-grey-700">{title}</Label> : null}
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{title ? <Label className="text-sm text-grey-700 cursor-pointer">{title}</Label> : null}
</div>
{helpText ? <p className="text-base text-grey-600">{helpText}</p> : null}
</div>
{helpText ? <p className="text-base text-grey-600">{helpText}</p> : null}
</div>
));
);
});
Checkbox.displayName = CheckboxPrimitive.Root.displayName;

export { Checkbox };