├── ittybitFileUpload ├── README.md ├── pages │ └── api │ │ ├── media.ts │ │ └── upload.ts └── components │ ├── MediaUpload.tsx │ └── MediaGallery.tsx ├── floating-navbar ├── Iconify.tsx ├── Card.tsx ├── Button.tsx └── Navbar.tsx ├── pulsing-button ├── style.css └── Button.tsx ├── expo-auth-flow ├── icons │ ├── MailIcon.tsx │ ├── AppleIcon.tsx │ └── GoogleIcon.tsx ├── components │ ├── ThemedText.tsx │ └── Button.tsx └── index.tsx ├── fadein-wrapper.tsx ├── basic-form └── form.tsx ├── apple-sign-in └── generate-apple-secret.ts └── liquid-glass └── LGCard.tsx /ittybitFileUpload/README.md: -------------------------------------------------------------------------------- 1 | This is an example using NextJS Page Router 2 | -------------------------------------------------------------------------------- /floating-navbar/Iconify.tsx: -------------------------------------------------------------------------------- 1 | import { Icon, IconifyIcon } from "@iconify/react"; 2 | import { BaseHTMLAttributes } from "react"; 3 | 4 | export interface IconifyProps extends BaseHTMLAttributes { 5 | icon: IconifyIcon | string; 6 | } 7 | 8 | export function Iconify({ icon, ...other }: IconifyProps) { 9 | return ( 10 | 11 | {/* Find icons here -> https://icon-sets.iconify.design/ */} 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pulsing-button/style.css: -------------------------------------------------------------------------------- 1 | .animation-pulse { 2 | position: relative; 3 | } 4 | 5 | .animation-pulse::before { 6 | content: ""; 7 | position: absolute; 8 | inset: -3px; 9 | border-radius: inherit; 10 | animation: pulse 2s infinite; 11 | } 12 | 13 | @keyframes pulse { 14 | 0% { 15 | box-shadow: 0 0 0 0 rgb(124, 58, 237); 16 | } 17 | 18 | 70% { 19 | box-shadow: 0 0 0 10px rgba(229, 62, 62, 0); 20 | } 21 | 22 | 100% { 23 | box-shadow: 0 0 0 0 rgba(229, 62, 62, 0); 24 | } 25 | } 26 | 27 | @layer utilities { 28 | .perspective-1000 { 29 | perspective: 1000px; 30 | } 31 | 32 | .transform-style-3d { 33 | transform-style: preserve-3d; 34 | } 35 | 36 | .backface-hidden { 37 | backface-visibility: hidden; 38 | } 39 | 40 | .rotate-y-180 { 41 | transform: rotateY(180deg); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /expo-auth-flow/icons/MailIcon.tsx: -------------------------------------------------------------------------------- 1 | import Svg, { Path } from "react-native-svg"; 2 | 3 | export function MailIcon() { 4 | return ( 5 | 6 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /floating-navbar/Card.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | type CardProps = { 4 | width?: string; 5 | cursor?: string; 6 | bgColor?: string; 7 | padding?: string; 8 | children: React.ReactNode; 9 | border?: string; 10 | borderColor?: string; 11 | className?: string; 12 | onClick?: (e: React.MouseEvent) => void; 13 | }; 14 | 15 | export default function Card({ 16 | width = "w-full", 17 | cursor = "default", 18 | bgColor = "bg-[linear-gradient(118deg,_#111_3.48%,_rgba(17,_17,_17,_0.00)_100%)]", 19 | padding = "p-4", 20 | children, 21 | onClick, 22 | border = "border-x border-t", 23 | borderColor = "border-[rgb(48,48,48)]", 24 | className, 25 | }: CardProps) { 26 | return ( 27 |
40 | {children} 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /expo-auth-flow/icons/AppleIcon.tsx: -------------------------------------------------------------------------------- 1 | import Svg, { Path } from "react-native-svg"; 2 | 3 | export function AppleIcon() { 4 | return ( 5 | 6 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /expo-auth-flow/icons/GoogleIcon.tsx: -------------------------------------------------------------------------------- 1 | import Svg, { Path } from "react-native-svg"; 2 | 3 | export function GoogleIcon() { 4 | return ( 5 | 6 | 10 | 14 | 18 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /fadein-wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import clsx from "clsx"; 4 | import { useEffect, useRef, useState } from "react"; 5 | 6 | interface FadeInProps { 7 | children: React.ReactNode; 8 | duration?: number; // delay in milliseconds 9 | className?: string; 10 | } 11 | 12 | export default function FadeIn({ 13 | children, 14 | duration = 0, 15 | className, 16 | }: FadeInProps) { 17 | const [isVisible, setIsVisible] = useState(false); 18 | const elementRef = useRef(null); 19 | 20 | useEffect(() => { 21 | const observer = new IntersectionObserver( 22 | ([entry]) => { 23 | if (entry.isIntersecting) { 24 | setTimeout(() => { 25 | setIsVisible(true); 26 | }, duration); 27 | } 28 | }, 29 | { 30 | threshold: 0.1, // Trigger when 10% of element is visible 31 | } 32 | ); 33 | 34 | if (elementRef.current) { 35 | observer.observe(elementRef.current); 36 | } 37 | 38 | return () => { 39 | if (elementRef.current) { 40 | observer.unobserve(elementRef.current); 41 | } 42 | }; 43 | }, [duration]); 44 | 45 | return ( 46 |
56 | {children} 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /expo-auth-flow/components/ThemedText.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, type TextProps } from "react-native"; 2 | 3 | import { useThemeColor } from "@/hooks/useThemeColor"; 4 | 5 | export type ThemedTextProps = TextProps & { 6 | lightColor?: string; 7 | darkColor?: string; 8 | type?: "default" | "title" | "defaultSemiBold" | "subtitle" | "link"; 9 | }; 10 | 11 | export function ThemedText({ 12 | style, 13 | lightColor, 14 | darkColor, 15 | type = "default", 16 | ...rest 17 | }: ThemedTextProps) { 18 | const color = useThemeColor({ light: lightColor, dark: darkColor }, "text"); 19 | 20 | return ( 21 | 33 | ); 34 | } 35 | 36 | const styles = StyleSheet.create({ 37 | default: { 38 | fontSize: 16, 39 | lineHeight: 24, 40 | }, 41 | defaultSemiBold: { 42 | fontSize: 16, 43 | lineHeight: 24, 44 | fontWeight: "600", 45 | }, 46 | title: { 47 | fontSize: 32, 48 | fontWeight: "bold", 49 | lineHeight: 32, 50 | }, 51 | subtitle: { 52 | fontSize: 20, 53 | fontWeight: "bold", 54 | }, 55 | link: { 56 | lineHeight: 30, 57 | fontSize: 16, 58 | color: "#0a7ea4", 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /pulsing-button/Button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { ButtonHTMLAttributes, ReactNode } from "react"; 3 | 4 | // Define variant types 5 | type ButtonVariant = "primary"; 6 | 7 | // Add icon position type 8 | type IconPosition = "left" | "right"; 9 | 10 | type ButtonProps = { 11 | children: string | ReactNode; 12 | variant?: ButtonVariant; 13 | icon?: ReactNode; 14 | iconPosition?: IconPosition; 15 | isLoading?: boolean; 16 | loaderColor?: string; 17 | isDisabled?: boolean; 18 | width?: string; 19 | padding?: string; 20 | textSize?: string; 21 | requiresSubscription?: boolean; 22 | isSubscribed?: boolean; 23 | } & ButtonHTMLAttributes; 24 | 25 | // Base button classes 26 | const baseButtonClasses = 27 | "h-fit py-2.5 px-4 text-[14px] font-bold min-w-[100px] text-center flex justify-center transition-all duration-300"; 28 | 29 | // Variant classes mapping 30 | const variantClasses: Record = { 31 | primary: 32 | "bg-[rgb(124, 58, 237)] text-white group-[.not-disabled]:hover:opacity-90 rounded-full", 33 | }; 34 | 35 | export default function Button({ 36 | children, 37 | variant = "primary", 38 | icon, 39 | iconPosition = "left", 40 | isLoading = false, 41 | loaderColor = "text-current", 42 | isDisabled = false, 43 | width = "w-fit", 44 | padding, 45 | textSize, 46 | className, 47 | requiresSubscription = false, 48 | isSubscribed, 49 | ...props 50 | }: ButtonProps) { 51 | return ( 52 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /expo-auth-flow/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText as Text } from "@/components/ThemedText"; 2 | import clsx from "clsx"; 3 | import React, { ReactNode } from "react"; 4 | import { ActivityIndicator, Pressable } from "react-native"; 5 | 6 | type Props = { 7 | onPress: () => void; 8 | children: ReactNode; 9 | isDisabled?: boolean; 10 | icon?: ReactNode; 11 | iconPosition?: "left" | "right"; 12 | variant?: "primary" | "secondary"; 13 | loading?: boolean; 14 | }; 15 | 16 | export default function Button({ 17 | onPress, 18 | children, 19 | isDisabled, 20 | icon, 21 | iconPosition = "left", 22 | variant = "primary", 23 | loading = false, 24 | }: Props) { 25 | return ( 26 | 37 | {loading ? ( 38 | 42 | ) : ( 43 | <> 44 | {icon && iconPosition === "left" && icon} 45 | 54 | {children} 55 | 56 | {icon && iconPosition === "right" && icon} 57 | 58 | )} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /ittybitFileUpload/pages/api/media.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | 3 | // https://ittybit.com/api/media/list 4 | const API_URL = "https://api.ittybit.com/media"; 5 | 6 | type QueryParams = { 7 | page?: string; 8 | limit?: string; 9 | }; 10 | 11 | export default async function handler( 12 | req: NextApiRequest, 13 | res: NextApiResponse 14 | ) { 15 | if (req.method !== "GET") { 16 | return res.status(405).json({ error: "Method not allowed" }); 17 | } 18 | 19 | try { 20 | const { page: pageParam, limit: limitParam } = req.query as QueryParams; 21 | 22 | const page = pageParam ? parseInt(pageParam, 10) : 1; 23 | const limit = limitParam ? parseInt(limitParam, 10) : 20; 24 | 25 | if (!process.env.ITTYBIT_API_KEY) { 26 | return res 27 | .status(500) 28 | .json({ error: "IttyBit API key not configured" }); 29 | } 30 | 31 | const url = new URL(API_URL); 32 | url.searchParams.set("page", page.toString()); 33 | url.searchParams.set("limit", limit.toString()); 34 | 35 | const response = await fetch(url.toString(), { 36 | headers: { 37 | Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`, 38 | }, 39 | }); 40 | 41 | if (!response.ok) { 42 | const errorData = await response.json().catch(() => ({})); 43 | 44 | console.error("IttyBit API Error:", { 45 | status: response.status, 46 | statusText: response.statusText, 47 | error: errorData, 48 | }); 49 | return res.status(response.status).json({ 50 | error: 51 | errorData.message || 52 | `Failed to fetch media: ${response.status} ${response.statusText}`, 53 | }); 54 | } 55 | 56 | const data = await response.json(); 57 | return res.status(200).json(data); 58 | } catch (error) { 59 | console.error("Media retrieval error:", error); 60 | return res.status(500).json({ 61 | error: 62 | error instanceof Error 63 | ? error.message 64 | : "Failed to retrieve media", 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /basic-form/form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | 5 | export default function Form() { 6 | const [state, setState] = useState({ 7 | firstName: "", 8 | lastName: "", 9 | email: "", 10 | message: "", 11 | }); 12 | 13 | const handleSubmit = (e: React.FormEvent) => { 14 | e.preventDefault(); 15 | console.log(state); 16 | 17 | alert("Form submitted"); 18 | }; 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 | 30 | setState({ 31 | ...state, 32 | firstName: e.target.value, 33 | }) 34 | } 35 | /> 36 | 37 | 42 | setState({ ...state, lastName: e.target.value }) 43 | } 44 | /> 45 |
46 | 47 | 52 | setState({ ...state, email: e.target.value }) 53 | } 54 | /> 55 | 56 |