├── .eslintrc.json ├── src ├── app │ ├── favicon.ico │ ├── fonts │ │ ├── GeistVF.woff │ │ └── GeistMonoVF.woff │ ├── twitter-image.png │ ├── opengraph-image.png │ ├── phone-input │ │ ├── twitter-image.png │ │ ├── opengraph-image.png │ │ └── page.tsx │ ├── select-pills │ │ ├── twitter-image.png │ │ ├── opengraph-image.png │ │ └── page.tsx │ ├── currency-select │ │ ├── twitter-image.png │ │ ├── opengraph-image.png │ │ └── page.tsx │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── lib │ ├── code │ │ ├── phone-input │ │ │ ├── installation.tsx │ │ │ ├── example-phone-connected.tsx │ │ │ └── component.tsx │ │ ├── country-dropdown │ │ │ ├── example.tsx │ │ │ ├── example-slim.tsx │ │ │ ├── example-multi.tsx │ │ │ ├── installation.tsx │ │ │ ├── example-form.tsx │ │ │ └── component.tsx │ │ ├── select-pills │ │ │ ├── misc.tsx │ │ │ ├── example-form.tsx │ │ │ └── component.tsx │ │ └── currency-select │ │ │ ├── misc.tsx │ │ │ ├── example-slim.tsx │ │ │ └── component.tsx │ ├── utils.ts │ ├── hooks │ │ └── use-copy-to-clipboard.tsx │ ├── constants │ │ └── currencies.tsx │ └── demos │ │ ├── select-pills │ │ ├── index.tsx │ │ └── pills-form.tsx │ │ ├── country-dropdown │ │ ├── example-slim.tsx │ │ ├── example.tsx │ │ └── example-multi.tsx │ │ ├── phone-input │ │ ├── simple.tsx │ │ ├── inline-country-dropdown.tsx │ │ └── index.tsx │ │ └── currency-select │ │ ├── example.tsx │ │ └── example-slim.tsx └── components │ ├── code-block.tsx │ ├── console.tsx │ ├── preview.tsx │ ├── theme-provider.tsx │ ├── footer.tsx │ ├── ui │ ├── label.tsx │ ├── input.tsx │ ├── sonner.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── button.tsx │ ├── card.tsx │ ├── tabs.tsx │ ├── dialog.tsx │ ├── form.tsx │ ├── command.tsx │ ├── select.tsx │ └── dropdown-menu.tsx │ ├── code.tsx │ ├── theme-toggle.tsx │ ├── header.tsx │ ├── copy-button.tsx │ ├── currency-select │ └── index.tsx │ ├── phone-input │ └── index.tsx │ ├── select-pills │ └── index.tsx │ └── country-dropdown │ └── index.tsx ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.ts ├── postcss.config.mjs ├── components.json ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── tailwind.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /src/app/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/twitter-image.png -------------------------------------------------------------------------------- /src/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/opengraph-image.png -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /src/app/phone-input/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/phone-input/twitter-image.png -------------------------------------------------------------------------------- /src/app/select-pills/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/select-pills/twitter-image.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/currency-select/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/currency-select/twitter-image.png -------------------------------------------------------------------------------- /src/app/phone-input/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/phone-input/opengraph-image.png -------------------------------------------------------------------------------- /src/app/select-pills/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/select-pills/opengraph-image.png -------------------------------------------------------------------------------- /src/app/currency-select/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uixmat/shadcn-country-dropdown/HEAD/src/app/currency-select/opengraph-image.png -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/code/phone-input/installation.tsx: -------------------------------------------------------------------------------- 1 | export const installation = ` 2 | \`\`\`bash 3 | # Install dependencies 4 | pnpm add react-circle-flags country-data-list libphonenumber-js 5 | \`\`\` 6 | `; 7 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/code/country-dropdown/example.tsx: -------------------------------------------------------------------------------- 1 | export const example = ` 2 | \`\`\`tsx 3 | void} 7 | /> 8 | \`\`\` 9 | `; 10 | -------------------------------------------------------------------------------- /src/lib/code/country-dropdown/example-slim.tsx: -------------------------------------------------------------------------------- 1 | export const exampleSlim = ` 2 | \`\`\`tsx {5} 3 | void} 7 | slim 8 | /> 9 | \`\`\` 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/code-block.tsx: -------------------------------------------------------------------------------- 1 | export const CodeBlock = ({ children }: { children: React.ReactNode }) => { 2 | return ( 3 |
4 | {children} 5 |
6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/code/country-dropdown/example-multi.tsx: -------------------------------------------------------------------------------- 1 | export const exampleMulti = ` 2 | \`\`\`tsx {3,5} 3 | void} 7 | multiple 8 | /> 9 | \`\`\` 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/console.tsx: -------------------------------------------------------------------------------- 1 | interface ConsoleProps { 2 | children: React.ReactNode; 3 | } 4 | 5 | export const Console = ({ children }: ConsoleProps) => { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/code/country-dropdown/installation.tsx: -------------------------------------------------------------------------------- 1 | export const installation = ` 2 | \`\`\`bash 3 | # Install dependencies 4 | pnpm add react-circle-flags country-data-list 5 | 6 | # Install shadcn components 7 | npx shadcn@latest add button 8 | npx shadcn@latest add command 9 | npx shadcn@latest add popover 10 | \`\`\` 11 | `; 12 | -------------------------------------------------------------------------------- /src/components/preview.tsx: -------------------------------------------------------------------------------- 1 | interface PreviewProps { 2 | children: React.ReactNode; 3 | } 4 | 5 | export const Preview = ({ children }: PreviewProps) => { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/code/select-pills/misc.tsx: -------------------------------------------------------------------------------- 1 | export const example = ` 2 | \`\`\`tsx 3 | 10 | \`\`\` 11 | `; 12 | 13 | export const installation = ` 14 | \`\`\`bash 15 | # Install shadcn components 16 | npx shadcn@latest add popover button badge 17 | \`\`\` 18 | `; 19 | -------------------------------------------------------------------------------- /src/lib/code/currency-select/misc.tsx: -------------------------------------------------------------------------------- 1 | export const example = ` 2 | \`\`\`tsx 3 | void} 5 | onCurrencySelect={() => void} 6 | name="currency" 7 | placeholder="Select currency" 8 | currencies="all" 9 | variant="default" 10 | /> 11 | \`\`\` 12 | `; 13 | 14 | export const installation = ` 15 | \`\`\`bash 16 | # Install dependencies 17 | pnpm add country-data-list 18 | 19 | # Install shadcn components 20 | npx shadcn@latest add select 21 | \`\`\` 22 | `; 23 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /src/components/footer.tsx: -------------------------------------------------------------------------------- 1 | export const Footer = () => { 2 | return ( 3 |
4 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/lib/hooks/use-copy-to-clipboard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | export function useCopyToClipboard({ 6 | timeout = 2000, 7 | onCopy, 8 | }: { 9 | timeout?: number; 10 | onCopy?: () => void; 11 | } = {}) { 12 | const [isCopied, setIsCopied] = React.useState(false); 13 | 14 | const copyToClipboard = (value: string) => { 15 | if (typeof window === "undefined" || !navigator.clipboard.writeText) { 16 | return; 17 | } 18 | 19 | if (!value) return; 20 | 21 | navigator.clipboard.writeText(value).then(() => { 22 | setIsCopied(true); 23 | 24 | if (onCopy) { 25 | onCopy(); 26 | } 27 | 28 | setTimeout(() => { 29 | setIsCopied(false); 30 | }, timeout); 31 | }, console.error); 32 | }; 33 | 34 | return { isCopied, copyToClipboard }; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /src/components/code.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { unified } from "unified"; 3 | import remarkParse from "remark-parse"; 4 | import remarkRehype from "remark-rehype"; 5 | import rehypeStringify from "rehype-stringify"; 6 | import rehypePrettyCode from "rehype-pretty-code"; 7 | 8 | export async function Code({ code }: { code: string }) { 9 | const highlightedCode = await highlightCode(code); 10 | return ( 11 |
16 | ); 17 | } 18 | 19 | async function highlightCode(code: string) { 20 | const file = await unified() 21 | .use(remarkParse) 22 | .use(remarkRehype) 23 | .use(rehypePrettyCode, { 24 | keepBackground: false, 25 | theme: "github-dark", 26 | }) 27 | .use(rehypeStringify) 28 | .process(code); 29 | 30 | return String(file); 31 | } 32 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner } from "sonner" 5 | 6 | type ToasterProps = React.ComponentProps 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme() 10 | 11 | return ( 12 | 28 | ) 29 | } 30 | 31 | export { Toaster } 32 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Moon, Sun } from "lucide-react"; 5 | import { useTheme } from "next-themes"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from "@/components/ui/dropdown-menu"; 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )) 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { ThemeProvider } from "@/components/theme-provider"; 3 | import { Toaster } from "@/components/ui/sonner"; 4 | import { Footer } from "@/components/footer"; 5 | import { Header } from "@/components/header"; 6 | import { Analytics } from "@vercel/analytics/react"; 7 | import localFont from "next/font/local"; 8 | import "./globals.css"; 9 | 10 | const geistSans = localFont({ 11 | src: "./fonts/GeistVF.woff", 12 | variable: "--font-geist-sans", 13 | weight: "100 900", 14 | }); 15 | const geistMono = localFont({ 16 | src: "./fonts/GeistMonoVF.woff", 17 | variable: "--font-geist-mono", 18 | weight: "100 900", 19 | }); 20 | 21 | export const metadata: Metadata = { 22 | title: "A shadcn/ui country dropdown", 23 | description: 24 | "An ISO 3166 compliant dropdown component for selecting a country.", 25 | }; 26 | 27 | export default function RootLayout({ 28 | children, 29 | }: Readonly<{ 30 | children: React.ReactNode; 31 | }>) { 32 | return ( 33 | 34 | 37 | 43 |
44 |
45 |
46 | {children} 47 |
48 |
49 |