← back to jeonghoon0126__covering-spot

Function bodies 369 total

All specs Real LLM only Function bodies
Process function · typescript · L109-L131 (23 LOC)
src/components/sections/Process.tsx
export function Process() {
  return (
    <section className="py-[120px] bg-bg-warm max-md:py-20" id="process">
      <div className="max-w-[1200px] mx-auto px-20 max-lg:px-10 max-sm:px-5">
        <ScrollReveal>
          <SectionHeader
            tag="이용 방법"
            title="간단한 4단계"
            desc="복잡한 절차 없이, 신청부터 수거까지 쉽고 빠르게"
            center
          />
        </ScrollReveal>
        <div className="grid grid-cols-4 gap-8 max-lg:grid-cols-2 max-lg:gap-6 max-sm:grid-cols-1 max-sm:gap-4">
          {processSteps.map((step, i) => (
            <ScrollReveal key={step.num} delay={i * 0.1}>
              <ProcessCard step={step} index={i} />
            </ScrollReveal>
          ))}
        </div>
      </div>
    </section>
  );
}
useCountUp function · typescript · L7-L43 (37 LOC)
src/components/sections/TrustBar.tsx
function useCountUp(target: string, duration = 1200) {
  const [display, setDisplay] = useState("0");
  const [started, setStarted] = useState(false);

  const start = useCallback(() => setStarted(true), []);

  useEffect(() => {
    if (!started) return;

    const numMatch = target.match(/[\d.]+/);
    if (!numMatch) {
      setDisplay(target);
      return;
    }

    const end = parseFloat(numMatch[0]);
    const isDecimal = target.includes(".");
    const prefix = target.slice(0, numMatch.index);
    const suffix = target.slice((numMatch.index ?? 0) + numMatch[0].length);
    const startTime = performance.now();

    function step(now: number) {
      const elapsed = now - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      const current = end * eased;
      setDisplay(
        prefix + (isDecimal ? current.toFixed(1) : Math.round(current).toString()) + suffix,
      );
      if (progress < 1) requestAnimation
step function · typescript · L28-L37 (10 LOC)
src/components/sections/TrustBar.tsx
    function step(now: number) {
      const elapsed = now - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      const current = end * eased;
      setDisplay(
        prefix + (isDecimal ? current.toFixed(1) : Math.round(current).toString()) + suffix,
      );
      if (progress < 1) requestAnimationFrame(step);
    }
StatItem function · typescript · L45-L80 (36 LOC)
src/components/sections/TrustBar.tsx
function StatItem({ value, suffix, label }: { value: string; suffix?: string; label: string }) {
  const { display, start } = useCountUp(value);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          start();
          observer.unobserve(el);
        }
      },
      { threshold: 0.5 },
    );
    observer.observe(el);
    return () => observer.disconnect();
  }, [start]);

  return (
    <div ref={ref} className="text-center">
      <div className="text-[36px] font-extrabold tracking-[-1px] leading-none mb-3 flex items-baseline gap-1 justify-center max-md:text-[28px] text-text-primary">
        {display}
        {suffix && (
          <span className="text-[20px] font-bold text-primary max-md:text-[16px]">
            {suffix}
          </span>
        )}
      </div>
      <div className="text-[14px] text-text-sub
TrustBar function · typescript · L82-L97 (16 LOC)
src/components/sections/TrustBar.tsx
export function TrustBar() {
  return (
    <div className="bg-bg-warm/60">
      <div className="max-w-[1200px] mx-auto px-20 max-lg:px-10 max-sm:px-5 flex items-center justify-center py-14 gap-16 max-md:gap-8 max-md:flex-wrap max-sm:gap-x-8 max-sm:gap-y-6 max-sm:py-10">
        {trustStats.map((stat, i) => (
          <div key={stat.label} className="contents">
            {i > 0 && (
              <div className="w-px h-10 bg-border/40 max-sm:hidden" />
            )}
            <StatItem value={stat.value} suffix={stat.suffix} label={stat.label} />
          </div>
        ))}
      </div>
    </div>
  );
}
Splash function · typescript · L8-L125 (118 LOC)
src/components/Splash.tsx
export function Splash({ children }: { children: React.ReactNode }) {
  const [phase, setPhase] = useState<"check" | "splash" | "done">("check");

  useEffect(() => {
    if (sessionStorage.getItem(STORAGE_KEY)) {
      setPhase("done");
      return;
    }

    // splash 표시
    setPhase("splash");

    // 0.8s fade-in + 0.3s fade-out = 1.1s 후 완료
    const timer = setTimeout(() => {
      sessionStorage.setItem(STORAGE_KEY, "1");
      setPhase("done");
    }, 1100);

    return () => clearTimeout(timer);
  }, []);

  // 초기 체크 중에는 아무것도 안 보여줌 (깜빡임 방지)
  if (phase === "check") {
    return null;
  }

  return (
    <>
      {phase === "splash" && (
        <div className="splash-overlay" aria-hidden="true">
          <div className="splash-content">
            <Image
              src="/images/logo.png"
              alt="커버링"
              width={56}
              height={56}
              className="splash-logo"
              priority
            />
            <span className="splash
ActionButton function · typescript · L49-L84 (36 LOC)
src/components/ui/ActionButton.tsx
  function ActionButton(
    {
      actionType = "caution",
      hierarchy = "primary",
      size = "md",
      disabled = false,
      children,
      className = "",
      ...rest
    },
    ref,
  ) {
    const classes = [
      "inline-flex items-center justify-center gap-2 font-semibold",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
      "cursor-pointer disabled:cursor-not-allowed",
      colorMap[actionType][hierarchy],
      sizeStyles[size],
      className,
    ]
      .filter(Boolean)
      .join(" ");

    return (
      <button
        ref={ref}
        type="button"
        disabled={disabled}
        className={classes}
        {...rest}
      >
        {children}
      </button>
    );
  },
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
InfoCircleIcon function · typescript · L46-L59 (14 LOC)
src/components/ui/Banner.tsx
function InfoCircleIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="1.5" />
      <path
        d="M10 9V14"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <circle cx="10" cy="6.5" r="0.75" fill="currentColor" />
    </svg>
  );
}
CheckCircleIcon function · typescript · L61-L74 (14 LOC)
src/components/ui/Banner.tsx
function CheckCircleIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="1.5" />
      <path
        d="M6.5 10L9 12.5L13.5 7.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
ErrorCircleIcon function · typescript · L76-L89 (14 LOC)
src/components/ui/Banner.tsx
function ErrorCircleIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="1.5" />
      <path
        d="M10 6V11"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <circle cx="10" cy="13.5" r="0.75" fill="currentColor" />
    </svg>
  );
}
WarningTriangleIcon function · typescript · L91-L109 (19 LOC)
src/components/ui/Banner.tsx
function WarningTriangleIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <path
        d="M10 2L18.66 17H1.34L10 2Z"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinejoin="round"
      />
      <path
        d="M10 8V12"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <circle cx="10" cy="14.5" r="0.75" fill="currentColor" />
    </svg>
  );
}
CloseSmallIcon function · typescript · L111-L128 (18 LOC)
src/components/ui/Banner.tsx
function CloseSmallIcon() {
  return (
    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
      <path
        d="M14 6L6 14"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
      <path
        d="M6 6L14 14"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
    </svg>
  );
}
Banner function · typescript · L138-L203 (66 LOC)
src/components/ui/Banner.tsx
export function Banner({
  variant = "information",
  title,
  description,
  action,
  onClose,
  className = "",
  icon,
}: BannerProps) {
  const styles = variantStyles[variant];

  const classes = [
    "relative flex gap-[--spacing-sm] p-[--spacing-md] rounded-md w-full",
    "transition-all duration-200",
    styles.container,
    className,
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <div className={classes} role="alert">
      {/* Icon */}
      <div className={`shrink-0 mt-0.5 ${styles.icon}`}>
        {icon || defaultIcons[variant]}
      </div>

      {/* Content */}
      <div className="flex-1 min-w-0">
        {title && (
          <p
            className={`text-[14px] leading-[22px] font-semibold ${styles.text}`}
          >
            {title}
          </p>
        )}
        <p
          className={`text-[14px] leading-[22px] font-normal ${styles.text} ${title ? "mt-0.5" : ""}`}
        >
          {description}
        </p>
        {action && (
        
Spinner function · typescript · L41-L66 (26 LOC)
src/components/ui/Button.tsx
function Spinner({ className }: { className?: string }) {
  return (
    <svg
      className={"animate-spin " + (className || "")}
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
    >
      <circle
        cx="8"
        cy="8"
        r="6"
        stroke="currentColor"
        strokeOpacity="0.25"
        strokeWidth="2"
      />
      <path
        d="M14 8a6 6 0 0 0-6-6"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
      />
    </svg>
  );
}
Button function · typescript · L70-L122 (53 LOC)
src/components/ui/Button.tsx
  function Button(
    {
      variant = "primary",
      size = "md",
      loading = false,
      fullWidth = false,
      disabled = false,
      href,
      children,
      className = "",
      type = "button",
      ...rest
    },
    ref,
  ) {
    const isDisabled = disabled || loading;

    const classes = [
      "inline-flex items-center justify-center gap-2 font-semibold",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
      "cursor-pointer disabled:cursor-not-allowed",
      variantStyles[variant],
      sizeStyles[size],
      fullWidth ? "w-full" : "",
      className,
    ]
      .filter(Boolean)
      .join(" ");

    /* Link variant */
    if (href && !isDisabled) {
      return (
        <Link href={href} className={classes}>
          {loading && <Spinner />}
          {children}
        </Link>
      );
    }

    return (
      <button
        ref={ref}
        type={type}
      
Want this analysis on your repo? https://repobility.com/scan/
CasualBanner function · typescript · L26-L158 (133 LOC)
src/components/ui/CasualBanner.tsx
export function CasualBanner({
  items,
  autoPlay = true,
  interval = 4000,
  className = "",
}: CasualBannerProps) {
  const [current, setCurrent] = useState(0);
  const [isDragging, setIsDragging] = useState(false);
  const [startX, setStartX] = useState(0);
  const [translateX, setTranslateX] = useState(0);
  const trackRef = useRef<HTMLDivElement>(null);
  const autoPlayRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const count = items.length;

  /* ─── Auto-play ─── */
  const stopAutoPlay = useCallback(() => {
    if (autoPlayRef.current) {
      clearInterval(autoPlayRef.current);
      autoPlayRef.current = null;
    }
  }, []);

  const startAutoPlay = useCallback(() => {
    if (!autoPlay || count <= 1) return;
    stopAutoPlay();
    autoPlayRef.current = setInterval(() => {
      setCurrent((prev) => (prev + 1) % count);
    }, interval);
  }, [autoPlay, count, interval, stopAutoPlay]);

  useEffect(() => {
    startAutoPlay();
    return stopAutoPlay;
  },
CheckIcon function · typescript · L14-L26 (13 LOC)
src/components/ui/Checkbox.tsx
function CheckIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
      <path
        d="M2.5 6L5 8.5L9.5 3.5"
        stroke="white"
        strokeWidth="1.8"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
IndeterminateIcon function · typescript · L28-L39 (12 LOC)
src/components/ui/Checkbox.tsx
function IndeterminateIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
      <path
        d="M3 6H9"
        stroke="white"
        strokeWidth="1.8"
        strokeLinecap="round"
      />
    </svg>
  );
}
Checkbox function · typescript · L43-L130 (88 LOC)
src/components/ui/Checkbox.tsx
  function Checkbox(
    {
      checked = false,
      indeterminate = false,
      disabled = false,
      error = false,
      label,
      className = "",
      ...rest
    },
    ref,
  ) {
    /* Handle indeterminate state on the native input */
    const internalRef = useRef<HTMLInputElement | null>(null);

    useEffect(() => {
      if (internalRef.current) {
        internalRef.current.indeterminate = indeterminate;
      }
    }, [indeterminate]);

    /* Merge refs */
    function setRefs(el: HTMLInputElement | null) {
      internalRef.current = el;
      if (typeof ref === "function") ref(el);
      else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el;
    }

    const isActive = checked || indeterminate;

    /* Box style */
    const boxColor = (() => {
      if (disabled && isActive) return "bg-disable-strong border-disable-strong";
      if (disabled) return "bg-disable-assistive border-disable-normal";
      if (error) return "border-se
setRefs function · typescript · L65-L69 (5 LOC)
src/components/ui/Checkbox.tsx
    function setRefs(el: HTMLInputElement | null) {
      internalRef.current = el;
      if (typeof ref === "function") ref(el);
      else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el;
    }
DeleteIcon function · typescript · L43-L60 (18 LOC)
src/components/ui/Chip.tsx
function DeleteIcon() {
  return (
    <svg
      width="14"
      height="14"
      viewBox="0 0 14 14"
      fill="none"
      className="shrink-0"
    >
      <path
        d="M4.5 4.5L9.5 9.5M9.5 4.5L4.5 9.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
    </svg>
  );
}
Chip function · typescript · L63-L113 (51 LOC)
src/components/ui/Chip.tsx
export function Chip({
  variant = "outline",
  size = "md",
  selected = false,
  disabled = false,
  onDelete,
  children,
  className = "",
  onClick,
  ...rest
}: ChipProps) {
  const style = variantStyles[variant];

  const classes = [
    "inline-flex items-center justify-center gap-1 font-medium",
    "rounded-full whitespace-nowrap",
    "transition-all duration-200",
    "cursor-pointer",
    sizeStyles[size],
    selected && variant === "outline" ? style.selected : style.base,
    disabled ? "opacity-40 cursor-not-allowed" : "",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <button
      type="button"
      disabled={disabled}
      className={classes}
      onClick={onClick}
      {...rest}
    >
      <span>{children}</span>
      {onDelete && (
        <span
          role="button"
          tabIndex={-1}
          onClick={(e) => {
            e.stopPropagation();
            if (!disabled) onDelete();
          }}
          className="inline-fle
MinusIcon function · typescript · L16-L27 (12 LOC)
src/components/ui/Counter.tsx
function MinusIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
      <path
        d="M3.5 8H12.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
    </svg>
  );
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
PlusIcon function · typescript · L29-L40 (12 LOC)
src/components/ui/Counter.tsx
function PlusIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
      <path
        d="M8 3.5V12.5M3.5 8H12.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
      />
    </svg>
  );
}
Counter function · typescript · L44-L144 (101 LOC)
src/components/ui/Counter.tsx
  function Counter(
    {
      value,
      onChange,
      min = 0,
      max = 99,
      disabled = false,
      label,
      className = "",
      ...rest
    },
    ref,
  ) {
    const atMin = value <= min;
    const atMax = value >= max;

    function decrement() {
      if (!atMin && !disabled) onChange(value - 1);
    }

    function increment() {
      if (!atMax && !disabled) onChange(value + 1);
    }

    const btnBase = [
      "flex items-center justify-center",
      "h-8 w-8 rounded-sm border",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
    ].join(" ");

    const btnEnabled =
      "border-border text-text-sub hover:bg-bg-warm cursor-pointer";
    const btnDisabled =
      "border-disable-alt text-disable-strong cursor-not-allowed";

    return (
      <div className="flex flex-col">
        {/* Label */}
        {label && (
          <label className="mb-2 text-sm font-semibold 
decrement function · typescript · L60-L62 (3 LOC)
src/components/ui/Counter.tsx
    function decrement() {
      if (!atMin && !disabled) onChange(value - 1);
    }
increment function · typescript · L64-L66 (3 LOC)
src/components/ui/Counter.tsx
    function increment() {
      if (!atMax && !disabled) onChange(value + 1);
    }
CTALink function · typescript · L13-L25 (13 LOC)
src/components/ui/CTALink.tsx
export function CTALink({ location, children, className }: Props) {
  return (
    <a
      href={KAKAO_CHAT_URL}
      target="_blank"
      rel="noopener noreferrer"
      className={className}
      onClick={() => track("cta_click", { location })}
    >
      {children}
    </a>
  );
}
Divider function · typescript · L20-L34 (15 LOC)
src/components/ui/Divider.tsx
export function Divider({ height = "xs", className = "" }: DividerProps) {
  return (
    <div
      role="separator"
      className={[
        "w-full shrink-0",
        "transition-all duration-200",
        heightStyles[height],
        className,
      ]
        .filter(Boolean)
        .join(" ")}
    />
  );
}
DotBadge function · typescript · L27-L45 (19 LOC)
src/components/ui/DotBadge.tsx
export function DotBadge({
  size = "sm",
  color = "primary",
  className = "",
}: DotBadgeProps) {
  return (
    <span
      className={[
        "absolute top-0 right-0 block rounded-full",
        "transition-all duration-200",
        sizeStyles[size],
        colorStyles[color],
        className,
      ]
        .filter(Boolean)
        .join(" ")}
    />
  );
}
ChevronDown function · typescript · L25-L43 (19 LOC)
src/components/ui/Dropdown.tsx
function ChevronDown({ className }: { className?: string }) {
  return (
    <svg
      width="20"
      height="20"
      viewBox="0 0 20 20"
      fill="none"
      className={className}
    >
      <path
        d="M5 7.5L10 12.5L15 7.5"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
Repobility · code-quality intelligence · https://repobility.com
Dropdown function · typescript · L47-L207 (161 LOC)
src/components/ui/Dropdown.tsx
  function Dropdown(
    {
      label,
      options,
      value,
      onChange,
      placeholder = "Select...",
      disabled = false,
      error = false,
      helperText,
      className = "",
      name,
    },
    ref,
  ) {
    const [open, setOpen] = useState(false);
    const [focused, setFocused] = useState(false);
    const containerRef = useRef<HTMLDivElement>(null);

    /* Close on outside click */
    useEffect(() => {
      if (!open) return;
      function handleClick(e: MouseEvent) {
        if (
          containerRef.current &&
          !containerRef.current.contains(e.target as Node)
        ) {
          setOpen(false);
        }
      }
      document.addEventListener("mousedown", handleClick);
      return () => document.removeEventListener("mousedown", handleClick);
    }, [open]);

    const selectedOption = options.find((o) => o.value === value);

    /* Border color by state */
    const borderColor = (() => {
      if (disabled) return "border-disable
handleClick function · typescript · L69-L76 (8 LOC)
src/components/ui/Dropdown.tsx
      function handleClick(e: MouseEvent) {
        if (
          containerRef.current &&
          !containerRef.current.contains(e.target as Node)
        ) {
          setOpen(false);
        }
      }
ChevronLeftIcon function · typescript · L16-L34 (19 LOC)
src/components/ui/GeneralHeader.tsx
function ChevronLeftIcon() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M15 19L8 12L15 5"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
GeneralHeader function · typescript · L37-L85 (49 LOC)
src/components/ui/GeneralHeader.tsx
export function GeneralHeader({
  title,
  onBack,
  rightAction,
  className = "",
  sticky = false,
  transparent = false,
}: GeneralHeaderProps) {
  const baseClasses = [
    "flex items-center h-14 px-[--spacing-md] gap-[--spacing-xs]",
    "transition-all duration-200",
  ];

  const stickyClasses = sticky
    ? "fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-border-light"
    : "";

  const bgClasses = transparent ? "bg-transparent" : sticky ? "" : "bg-white";

  const classes = [...baseClasses, stickyClasses, bgClasses, className]
    .filter(Boolean)
    .join(" ");

  return (
    <header className={classes}>
      {/* Back button */}
      {onBack && (
        <button
          type="button"
          onClick={onBack}
          className="inline-flex items-center justify-center w-10 h-10 -ml-2 rounded-full text-text-primary hover:bg-bg-warm2 transition-all duration-200 cursor-pointer shrink-0"
          aria-label="뒤로가기"
        >
          <Chev
IconButton function · typescript · L36-L74 (39 LOC)
src/components/ui/IconButton.tsx
  function IconButton(
    {
      variant = "tertiary",
      size = "md",
      shape = "circle",
      disabled = false,
      children,
      className = "",
      ...rest
    },
    ref,
  ) {
    const radiusClass = shape === "circle" ? "rounded-full" : "rounded-md";

    const classes = [
      "inline-flex items-center justify-center shrink-0",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
      "cursor-pointer disabled:cursor-not-allowed",
      variantStyles[variant],
      sizeStyles[size],
      radiusClass,
      className,
    ]
      .filter(Boolean)
      .join(" ");

    return (
      <button
        ref={ref}
        type="button"
        disabled={disabled}
        className={classes}
        {...rest}
      >
        {children}
      </button>
    );
  },
IndicatorDot function · typescript · L12-L54 (43 LOC)
src/components/ui/IndicatorDot.tsx
export function IndicatorDot({
  total,
  current,
  className = "",
  onChange,
}: IndicatorDotProps) {
  return (
    <div
      className={[
        "inline-flex items-center gap-2",
        className,
      ]
        .filter(Boolean)
        .join(" ")}
      role="tablist"
    >
      {Array.from({ length: total }, (_, i) => {
        const isActive = i === current;

        return (
          <button
            key={i}
            type="button"
            role="tab"
            aria-selected={isActive}
            aria-label={`${i + 1} / ${total}`}
            onClick={() => onChange?.(i)}
            className={[
              "block rounded-full shrink-0",
              "transition-all duration-200 ease-out",
              isActive
                ? "w-4 h-1.5 bg-brand-400"
                : "w-1.5 h-1.5 bg-border hover:bg-fill-assistive",
              onChange ? "cursor-pointer" : "cursor-default",
            ]
              .filter(Boolean)
              .join(" ")}
     
CheckIcon function · typescript · L13-L30 (18 LOC)
src/components/ui/IndicatorNumber.tsx
function CheckIcon() {
  return (
    <svg
      width="14"
      height="14"
      viewBox="0 0 14 14"
      fill="none"
    >
      <path
        d="M3.5 7L6 9.5L10.5 4.5"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
IndicatorNumber function · typescript · L33-L96 (64 LOC)
src/components/ui/IndicatorNumber.tsx
export function IndicatorNumber({
  step,
  total,
  current,
  label,
  className = "",
}: IndicatorNumberProps) {
  const isCompleted = step < current;
  const isActive = step === current;
  /* const isUpcoming = step > current; */

  /* Circle style */
  const circleStyle = (() => {
    if (isCompleted) return "bg-brand-400 text-white";
    if (isActive) return "bg-brand-400 text-white";
    return "bg-[#DEE3ED] text-[#8A96A8]";
  })();

  /* Label style */
  const labelStyle = (() => {
    if (isActive) return "text-brand-700 font-semibold";
    if (isCompleted) return "text-text-sub";
    return "text-[#8A96A8]";
  })();

  return (
    <div
      className={[
        "inline-flex flex-col items-center gap-1.5",
        "transition-all duration-200",
        className,
      ]
        .filter(Boolean)
        .join(" ")}
      aria-label={`Step ${step} of ${total}${label ? `: ${label}` : ""}`}
      aria-current={isActive ? "step" : undefined}
    >
      {/* Number circle */}
   
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
KakaoIcon function · typescript · L1-L12 (12 LOC)
src/components/ui/KakaoIcon.tsx
export function KakaoIcon({ size = 20 }: { size?: number }) {
  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 20 20"
      fill="currentColor"
    >
      <path d="M10 1C5.03 1 1 4.13 1 8c0 2.39 1.56 4.49 3.92 5.73-.12.44-.78 2.85-.81 3.04 0 0-.02.15.07.21.1.06.21.01.21.01.28-.04 3.24-2.1 3.75-2.46.28.04.57.06.86.06 4.97 0 9-3.13 9-7 0-3.87-4.03-7-9-7z" />
    </svg>
  );
}
Label function · typescript · L41-L62 (22 LOC)
src/components/ui/Label.tsx
export function Label({
  variant = "primary",
  size = "md",
  children,
  className = "",
}: LabelProps) {
  return (
    <span
      className={[
        "inline-flex items-center justify-center font-semibold whitespace-nowrap",
        "transition-all duration-200",
        variantStyles[variant],
        sizeStyles[size],
        className,
      ]
        .filter(Boolean)
        .join(" ")}
    >
      {children}
    </span>
  );
}
LinkButton function · typescript · L25-L72 (48 LOC)
src/components/ui/LinkButton.tsx
  function LinkButton(
    {
      size = "md",
      disabled = false,
      href,
      icon,
      children,
      className = "",
      ...rest
    },
    ref,
  ) {
    const classes = [
      "inline-flex items-center gap-1 font-semibold",
      "text-brand-400 hover:text-brand-300",
      "underline-offset-2 hover:underline",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
      "cursor-pointer",
      disabled ? "opacity-40 pointer-events-none" : "",
      sizeStyles[size],
      className,
    ]
      .filter(Boolean)
      .join(" ");

    if (href && !disabled) {
      return (
        <Link href={href} className={classes}>
          {children}
          {icon && <span className="inline-flex shrink-0">{icon}</span>}
        </Link>
      );
    }

    return (
      <button
        ref={ref}
        type="button"
        disabled={disabled}
        className={classes}
        {...rest}
     
LoadingSpinner function · typescript · L28-L76 (49 LOC)
src/components/ui/LoadingSpinner.tsx
export function LoadingSpinner({
  size = "md",
  color = "primary",
  className = "",
}: LoadingSpinnerProps) {
  const px = sizeMap[size];
  const stroke = colorMap[color];
  const r = px / 2 - 2;
  const circumference = 2 * Math.PI * r;

  return (
    <svg
      width={px}
      height={px}
      viewBox={`0 0 ${px} ${px}`}
      fill="none"
      className={[
        "animate-spin",
        "transition-all duration-200",
        className,
      ]
        .filter(Boolean)
        .join(" ")}
      style={{ animationDuration: "0.8s" }}
    >
      {/* Background track */}
      <circle
        cx={px / 2}
        cy={px / 2}
        r={r}
        stroke={stroke}
        strokeOpacity="0.2"
        strokeWidth="2.5"
        fill="none"
      />
      {/* Active arc */}
      <circle
        cx={px / 2}
        cy={px / 2}
        r={r}
        stroke={stroke}
        strokeWidth="2.5"
        strokeLinecap="round"
        strokeDasharray={`${circumference * 0.7} ${circumference * 0.
MessageBox function · typescript · L40-L83 (44 LOC)
src/components/ui/MessageBox.tsx
export function MessageBox({
  variant = "received",
  children,
  timestamp,
  className = "",
}: MessageBoxProps) {
  const styles = variantStyles[variant];

  const wrapperClasses = [
    styles.wrapper,
    "transition-all duration-200",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  const isSystem = variant === "system";

  return (
    <div className={wrapperClasses}>
      <div className={`max-w-[70%] ${isSystem ? "max-w-full" : ""}`}>
        {/* Bubble */}
        <div className={styles.bubble}>
          <div
            className={
              isSystem
                ? "text-[12px] leading-[18px] text-center"
                : "text-[14px] leading-[22px]"
            }
          >
            {children}
          </div>
        </div>

        {/* Timestamp */}
        {timestamp && !isSystem && (
          <p className={`text-[11px] leading-[16px] mt-1 ${styles.time}`}>
            {timestamp}
          </p>
        )}
      </div>
    </div>
  );
}
MessageItem function · typescript · L17-L80 (64 LOC)
src/components/ui/MessageItem.tsx
export function MessageItem({
  icon,
  title,
  description,
  timestamp,
  unread = false,
  onClick,
  className = "",
}: MessageItemProps) {
  const Tag = onClick ? "button" : "div";

  const classes = [
    "relative flex flex-row w-full px-[--spacing-lg] py-[--spacing-md] gap-[--spacing-sm]",
    "transition-all duration-200",
    onClick ? "cursor-pointer hover:bg-bg-warm active:bg-bg-warm2 text-left" : "",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <Tag
      {...(onClick ? { type: "button" as const, onClick } : {})}
      className={classes}
    >
      {/* Icon */}
      {icon !== undefined && (
        <div className="relative shrink-0">
          <div className="w-10 h-10 rounded-full bg-bg-warm2 flex items-center justify-center">
            {icon}
          </div>
          {/* Unread dot */}
          {unread && (
            <span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full bg-brand-400" />
          )}
        </div>
  
CloseIcon function · typescript · L12-L37 (26 LOC)
src/components/ui/ModalHeader.tsx
function CloseIcon() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M18 6L6 18"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6 6L18 18"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}
ModalHeader function · typescript · L40-L82 (43 LOC)
src/components/ui/ModalHeader.tsx
export function ModalHeader({
  title,
  onClose,
  subtitle,
  className = "",
}: ModalHeaderProps) {
  const classes = [
    "flex items-center justify-between h-14 px-[--spacing-lg]",
    "border-b border-[#DEE3ED]",
    "transition-all duration-200",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <header className={classes}>
      {/* Title group */}
      <div className="flex flex-col min-w-0">
        <h2 className="text-[18px] leading-[26px] font-bold text-text-primary truncate">
          {title}
        </h2>
        {subtitle && (
          <p className="text-[14px] leading-[22px] font-normal text-text-sub truncate">
            {subtitle}
          </p>
        )}
      </div>

      {/* Close button */}
      {onClose && (
        <button
          type="button"
          onClick={onClose}
          className="inline-flex items-center justify-center w-10 h-10 -mr-2 rounded-full text-text-muted hover:text-text-primary hover:bg-bg-warm2 transition-all
Want this analysis on your repo? https://repobility.com/scan/
Chevron function · typescript · L28-L47 (20 LOC)
src/components/ui/NavButton.tsx
function Chevron({ direction, size }: { direction: Direction; size: number }) {
  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
    >
      {direction === "left" ? (
        <path d="M15 18l-6-6 6-6" />
      ) : (
        <path d="M9 18l6-6-6-6" />
      )}
    </svg>
  );
}
NavButton function · typescript · L51-L87 (37 LOC)
src/components/ui/NavButton.tsx
  function NavButton(
    {
      direction = "left",
      size = "md",
      disabled = false,
      className = "",
      ...rest
    },
    ref,
  ) {
    const classes = [
      "inline-flex items-center justify-center shrink-0 rounded-full",
      "bg-white border border-border text-text-primary",
      "hover:bg-bg-warm2",
      "transition-all duration-200",
      "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-400",
      "cursor-pointer disabled:cursor-not-allowed disabled:opacity-40",
      "shadow-sm",
      sizeStyles[size],
      className,
    ]
      .filter(Boolean)
      .join(" ");

    return (
      <button
        ref={ref}
        type="button"
        disabled={disabled}
        className={classes}
        aria-label={direction === "left" ? "Previous" : "Next"}
        {...rest}
      >
        <Chevron direction={direction} size={iconSizes[size]} />
      </button>
    );
  },
NumberBadge function · typescript · L28-L55 (28 LOC)
src/components/ui/NumberBadge.tsx
export function NumberBadge({
  count,
  maxCount = 99,
  size = "sm",
  variant = "primary",
  className = "",
}: NumberBadgeProps) {
  if (count <= 0) return null;

  const display = count > maxCount ? `${maxCount}+` : String(count);

  return (
    <span
      className={[
        "inline-flex items-center justify-center px-1 font-bold leading-none",
        "rounded-full",
        "transition-all duration-200",
        sizeStyles[size],
        variantStyles[variant],
        className,
      ]
        .filter(Boolean)
        .join(" ")}
    >
      {display}
    </span>
  );
}
‹ prevpage 5 / 8next ›