Skip to content
Merged
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
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@
# OS files
**/.DS_Store
*.log
package-lock.json
package-lock.json

# Agents files
CLAUDE.md
AGENTS.md
.claude/
.claudeignore
70 changes: 59 additions & 11 deletions components/EditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Popover,
PopoverContent,
PopoverTrigger,
AnimatedCount,
} from "@betterinternship/components";
import { Calendar } from "@/components/ui/calendar";
import {
Expand All @@ -25,6 +26,7 @@ import * as RadioGroup from "@radix-ui/react-radio-group";
import {
CalendarDays,
Check,
CheckCheck,
ChevronDown,
ChevronLeft,
ChevronRight,
Expand All @@ -33,14 +35,14 @@ import {
Minus,
} from "lucide-react";
import * as React from "react";
import { createContext, useContext, useRef } from "react";
import { createContext, useContext, useEffect, useRef } from "react";
import "react-datepicker/dist/react-datepicker.css";
import { GroupableRadioDropdown } from "./ui/dropdown";
import { Input } from "@betterinternship/components";
import { Tooltip } from "react-tooltip";
import { Textarea } from "./ui/textarea";
import { Matcher } from "react-day-picker";
import { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { useBlurTransition } from "./animata/blur";

interface EditFormContext<T extends IFormData> {
Expand Down Expand Up @@ -439,14 +441,26 @@ export const FormCheckbox = ({
interface FormCheckBoxGroupProps extends React.InputHTMLAttributes<HTMLInputElement> {
options: { value: string | number; label: string; description?: string }[];
values: (string | number)[];
setter: (value: any) => void;
setter: (values: (string | number)[]) => void;
label?: string;
required?: boolean;
className?: string;
tooltip?: string;
tooltipId?: string;
/** Multi-select hint shown above the options. Pass `null` to hide it. */
hint?: React.ReactNode;
/** Columns on md+ screens. Defaults to 3. */
columns?: 1 | 2 | 3;
/** Fired when a single option is toggled, before the setter runs. */
onToggle?: (value: string | number, checked: boolean) => void;
}

const GRID_COLS: Record<1 | 2 | 3, string> = {
1: "",
2: "md:grid-cols-2",
3: "md:grid-cols-3",
};

export const FormCheckBoxGroup = ({
options,
values,
Expand All @@ -456,16 +470,23 @@ export const FormCheckBoxGroup = ({
className,
tooltip,
tooltipId,
hint = "Select all that apply",
columns = 3,
onToggle,
...props
}: FormCheckBoxGroupProps) => {
const handleValueChange = (optionValue: string | number) => {
if (values.includes(optionValue)) {
setter(values.filter((v) => v !== optionValue));
} else {
setter([...values, optionValue]);
}
const willBeChecked = !values.includes(optionValue);
onToggle?.(optionValue, willBeChecked);
setter(
willBeChecked
? [...values, optionValue]
: values.filter((v) => v !== optionValue),
);
};

const blurTransition = useBlurTransition();

return (
<div className={cn("space-y-3", className)}>
{label && (
Expand All @@ -477,16 +498,43 @@ export const FormCheckBoxGroup = ({
/>
)}

<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{(hint || values.length > 0) && (
<div className="flex items-center gap-2 text-xs">
{hint && (
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 font-medium text-muted-foreground">
<CheckCheck className="h-3 w-3" />
{hint}
</span>
)}
<AnimatePresence>
{values.length > 0 && (
<motion.span
className="font-medium text-primary"
{...blurTransition}
>
<AnimatedCount value={values.length} /> selected
</motion.span>
)}
</AnimatePresence>
</div>
)}

<div className={cn("grid grid-cols-1 gap-4", GRID_COLS[columns])}>
{options.map((option) => {
const isChecked = values.includes(option.value);

return (
<div
key={option.value}
role="checkbox"
aria-checked={isChecked}
onClick={() => handleValueChange(option.value)}
className={`flex items-start gap-4 p-3 border rounded-[0.33em] transition-colors cursor-pointer h-fit
${isChecked ? "border-primary border-opacity-85" : "border-gray-200 hover:border-gray-300"}`}
className={cn(
"flex items-start gap-3 p-3 border rounded-[0.33em] transition-colors cursor-pointer h-fit",
isChecked
? "border-primary border-opacity-85 bg-primary/5"
: "border-gray-200 hover:border-gray-300 hover:bg-gray-50",
)}
>
<FormCheckbox checked={isChecked ?? false} />
<div className="grid grid-rows-1 md:grid-rows-2">
Expand Down
22 changes: 5 additions & 17 deletions components/features/hire/listings/create-job-steps/BasicStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { FormInput } from "@/components/EditForm";
import { GroupableRadioDropdown } from "@/components/ui/dropdown";
import { Job } from "@/lib/db/db.types";
import { Card, PageHeader } from "@betterinternship/components";
import { AnimatedCount, Card, PageHeader } from "@betterinternship/components";
import { Input } from "@betterinternship/components";
import { Textarea } from "@/components/ui/textarea";
import { BasicStepIllustration } from "./illustrations/BasicStepIllustration";
Expand Down Expand Up @@ -99,15 +99,10 @@ const BasicStep = ({
maxLength={100}
className="h-10"
/>
<div className="flex justify-between gap-4">
<p className="text-xs text-muted-foreground">
Keep it clear and specific. “Frontend Intern” works better than
“Intern”.
</p>
<p className="text-xs text-muted-foreground tabular-nums shrink-0">
{titleLength}/100
</p>
</div>
<p className="text-xs text-muted-foreground tabular-nums shrink-0 text-right">
<AnimatedCount value={titleLength} />
/100
</p>
</div>

{/* Location */}
Expand All @@ -121,9 +116,6 @@ const BasicStep = ({
maxLength={100}
className="h-10"
/>
<p className="text-xs text-muted-foreground">
Where will the intern work? You can change this later.
</p>
</div>

{/* Category */}
Expand All @@ -134,10 +126,6 @@ const BasicStep = ({
</span>
<span className="text-destructive text-xs">*</span>
</div>
<p className="text-xs text-muted-foreground -mt-1">
Choose a category that describes the job (like Cybersecurity, Legal,
Design, etc.).
</p>
<GroupableRadioDropdown
name="category"
defaultValue={categoryValue}
Expand Down
Loading