├── .changyrc ├── .github └── workflows │ ├── build-registry.yml │ ├── changy.yml │ └── ci.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── components.json ├── eslint.config.js ├── jsrepo-manifest.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── src ├── app.d.ts ├── app.html ├── app.pcss ├── hooks.client.ts ├── lib │ ├── components │ │ └── ui │ │ │ ├── button │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ │ ├── command │ │ │ ├── command-dialog.svelte │ │ │ ├── command-empty.svelte │ │ │ ├── command-group.svelte │ │ │ ├── command-input.svelte │ │ │ ├── command-item.svelte │ │ │ ├── command-link-item.svelte │ │ │ ├── command-list.svelte │ │ │ ├── command-separator.svelte │ │ │ ├── command-shortcut.svelte │ │ │ ├── command.svelte │ │ │ └── index.ts │ │ │ ├── dialog │ │ │ ├── dialog-content.svelte │ │ │ ├── dialog-description.svelte │ │ │ ├── dialog-footer.svelte │ │ │ ├── dialog-header.svelte │ │ │ ├── dialog-overlay.svelte │ │ │ ├── dialog-portal.svelte │ │ │ ├── dialog-title.svelte │ │ │ └── index.ts │ │ │ ├── dropdown-menu │ │ │ ├── dropdown-menu-checkbox-item.svelte │ │ │ ├── dropdown-menu-content.svelte │ │ │ ├── dropdown-menu-group-heading.svelte │ │ │ ├── dropdown-menu-item.svelte │ │ │ ├── dropdown-menu-label.svelte │ │ │ ├── dropdown-menu-radio-group.svelte │ │ │ ├── dropdown-menu-radio-item.svelte │ │ │ ├── dropdown-menu-separator.svelte │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ ├── dropdown-menu-sub-content.svelte │ │ │ ├── dropdown-menu-sub-trigger.svelte │ │ │ └── index.ts │ │ │ ├── label │ │ │ ├── index.ts │ │ │ └── label.svelte │ │ │ ├── link │ │ │ ├── index.ts │ │ │ └── link.svelte │ │ │ ├── phone-input │ │ │ ├── country-selector.svelte │ │ │ ├── flag.svelte │ │ │ ├── index.ts │ │ │ └── phone-input.svelte │ │ │ ├── popover │ │ │ ├── index.ts │ │ │ └── popover-content.svelte │ │ │ ├── scroll-area │ │ │ ├── index.ts │ │ │ ├── scroll-area-scrollbar.svelte │ │ │ └── scroll-area.svelte │ │ │ ├── separator │ │ │ ├── index.ts │ │ │ └── separator.svelte │ │ │ ├── snippet │ │ │ ├── copy-button.svelte │ │ │ ├── copy-icon.svelte │ │ │ ├── index.ts │ │ │ └── snippet.svelte │ │ │ ├── sonner │ │ │ ├── index.ts │ │ │ └── sonner.svelte │ │ │ ├── tabs │ │ │ ├── index.ts │ │ │ ├── tabs-content.svelte │ │ │ ├── tabs-list.svelte │ │ │ └── tabs-trigger.svelte │ │ │ └── theme-selector │ │ │ ├── index.ts │ │ │ └── theme-selector.svelte │ ├── ts │ │ └── context-provider.ts │ └── utils.ts └── routes │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ ├── changelog │ ├── +page.server.ts │ └── +page.svelte │ └── example-container.svelte ├── static └── favicon.png ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json └── vite.config.ts /.changyrc: -------------------------------------------------------------------------------- 1 | { 2 | "timezone": "UTC", 3 | "path": "CHANGELOG.md", 4 | "changeCategories": [ 5 | "Added", 6 | "Changed", 7 | "Fixed" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/build-registry.yml: -------------------------------------------------------------------------------- 1 | name: build-registry 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - uses: pnpm/action-setup@v4 24 | with: 25 | version: 9.12.3 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: '20' 29 | 30 | - name: Install Dependencies 31 | run: pnpm install 32 | 33 | - name: Build jsrepo-manifest.json 34 | run: pnpm build:registry 35 | 36 | - name: Create pull request with changes 37 | uses: peter-evans/create-pull-request@v7 38 | with: 39 | title: 'chore: update `jsrepo-manifest.json`' 40 | body: | 41 | - Update `jsrepo-manifest.json` 42 | 43 | --- 44 | This PR was auto generated 45 | branch: build-manifest 46 | commit-message: build `jsrepo-manifest.json` 47 | -------------------------------------------------------------------------------- /.github/workflows/changy.yml: -------------------------------------------------------------------------------- 1 | name: changy 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | changy: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor != 'github-actions[bot]' }} 12 | permissions: 13 | pull-requests: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.head_ref }} 19 | - uses: pnpm/action-setup@v4 20 | with: 21 | version: 9.12.3 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: '20' 25 | 26 | - name: Get Latest Changes 🆕 27 | run: | 28 | output=$(npx changy@latest latest --today) 29 | if [ -z "$output" ]; then 30 | echo "CHANGELOG_EMPTY=true" >> $GITHUB_ENV 31 | else 32 | echo "CHANGELOG_EMPTY=false" >> $GITHUB_ENV 33 | echo "CHANGELOG<> $GITHUB_ENV 34 | echo "$output" >> $GITHUB_ENV 35 | echo "EOF" >> $GITHUB_ENV 36 | fi 37 | 38 | - name: Get current date 39 | run: echo "CURRENT_DATETIME=$(TZ=UTC date +'%Y-%m-%d %I:%M %p')" >> $GITHUB_ENV 40 | 41 | - name: Comment no CHANGELOG found 42 | if: env.CHANGELOG_EMPTY == 'true' 43 | uses: mshick/add-pr-comment@v2 44 | with: 45 | message: | 46 | :warning: No changelogs found 47 | 48 | **If your PR includes user facing changes make sure to add them with `npx changy add`.** 49 | 50 | ### How to add a changelog 51 | ```bash 52 | npx changy add 53 | ``` 54 | 55 | _(UTC) ${{ env.CURRENT_DATETIME }}_ 56 | repo-token: ${{ secrets.GITHUB_TOKEN }} 57 | allow-repeats: false 58 | 59 | - name: Comment CHANGELOG 60 | if: env.CHANGELOG_EMPTY == 'false' 61 | uses: mshick/add-pr-comment@v2 62 | with: 63 | message: | 64 | # Found changelogs 65 | 66 | ```md 67 | ${{ env.CHANGELOG }} 68 | ``` 69 | 70 | > [!NOTE] 71 | > If you didn't add these changes it could be from a previous commit today. 72 | 73 | _(UTC) ${{ env.CURRENT_DATETIME }}_ 74 | repo-token: ${{ secrets.GITHUB_TOKEN }} 75 | allow-repeats: false 76 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | CI: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: pnpm/action-setup@v4 15 | with: 16 | version: 9.12.3 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | 21 | - name: Install dependencies 22 | run: pnpm install 23 | 24 | - name: Format 25 | run: pnpm format 26 | 27 | - name: Lint 28 | run: pnpm lint 29 | 30 | - name: Build registry 31 | run: pnpm build:registry 32 | 33 | - name: Build 34 | run: pnpm build 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /.svelte-kit 8 | /build 9 | 10 | # OS 11 | .DS_Store 12 | Thumbs.db 13 | 14 | # Env 15 | .env 16 | .env.* 17 | !.env.example 18 | !.env.test 19 | 20 | # Vite 21 | vite.config.js.timestamp-* 22 | vite.config.ts.timestamp-* 23 | package-lock.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "printWidth": 80, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["ctry"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2024.11.23 2 | 3 | ## Added 4 | 5 | - You can now add the phone-input by running `npx jsrepo add github/ieedan/shadcn-phone-input-svelte/ui/phone-input`. 6 | 7 | # 2024.11.1 8 | 9 | ## Changed 10 | 11 | - Svelte 5 🎉 12 | 13 | # 2024.8.13 14 | 15 | ## Added 16 | 17 | - Added component code to the page 18 | - Added CHANGELOG.md 19 | - Added changelog page at `/changelog` as well as navigation to and from it 20 | 21 | ## Changed 22 | 23 | - You can now copy code for your specific package manager 24 | 25 | ## Fixed 26 | 27 | - Sonner showing up as the wrong color 28 | - Fixed scroll overflow behavior for tabs on smaller screens 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Aidan Bleser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## (MOVED) See [shadcn-svelte-extras](https://github.com/ieedan/shadcn-svelte-extras) 2 | This component is now maintained in [shadcn-svelte-extras](https://github.com/ieedan/shadcn-svelte-extras) see there for the latest updates. 3 | 4 | [![jsrepo](https://jsrepo.dev/badges/build/passing.svg)](https://jsrepo.dev) 5 | 6 | ![image](https://github.com/user-attachments/assets/aa551a41-2255-4536-a387-18e5378ef1f4) 7 | 8 | # shadcn-phone-input-svelte 9 | 10 | A Svelte port of [shadcn-phone-input](https://github.com/omeralpi/shadcn-phone-input). 11 | 12 | ## Setup 13 | 14 | ### Install shadcn-svelte via CLI 15 | 16 | Run the `shadcn-svelte` init command to setup your project: 17 | 18 | ```bash 19 | npx shadcn-svelte@next init 20 | ``` 21 | 22 | ### Install necessary components 23 | 24 | Run the `shadcn-svelte` add command to add the necessary components to your project: 25 | 26 | ```bash 27 | npx shadcn-svelte@next add button 28 | npx shadcn-svelte@next add command 29 | npx shadcn-svelte@next add popover 30 | npx shadcn-svelte@next add scroll-area 31 | ``` 32 | 33 | ## Automatic 34 | 35 | ```bash 36 | npx jsrepo add github/ieedan/shadcn-phone-input-svelte/ui/phone-input 37 | ``` 38 | 39 | or 40 | 41 | ## Manual 42 | 43 | ### Install Svelte Tel Input 44 | 45 | ```bash 46 | npm install svelte-tel-input 47 | ``` 48 | 49 | ### Copy the code 50 | 51 | You can find the most recent code [here](https://github.com/ieedan/shadcn-phone-input-svelte/tree/main/src/lib/components/ui/phone-input). 52 | 53 | ## Credits 54 | 55 | Huge thanks to the creators of [svelte-tel-input](https://github.com/gyurielf/svelte-tel-input) for making this possible. 56 | 57 | ## Contributing 58 | 59 | Contributions as always are welcomed. 60 | 61 | ### Changelog 62 | 63 | When contributing you'll want to add any changes you make to the changelog you can do this by running: 64 | 65 | ```bash 66 | npx changy add 67 | # or 68 | npm run changelog:new 69 | ``` 70 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src/app.css", 7 | "baseColor": "zinc" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils", 12 | "ui": "$lib/components/ui", 13 | "hooks": "$lib/hooks" 14 | }, 15 | "typescript": true, 16 | "registry": "https://next.shadcn-svelte.com/registry" 17 | } 18 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | import pluginUnicorn from 'eslint-plugin-unicorn'; 7 | import cspellPlugin from '@cspell/eslint-plugin'; 8 | 9 | const enforceUpperCaseForAsConstRule = { 10 | meta: { 11 | type: 'suggestion', 12 | docs: { 13 | description: "Enforce UPPER_CASE for constants marked with 'as const'", 14 | category: 'Stylistic Issues', 15 | recommended: false, 16 | }, 17 | fixable: null, 18 | }, 19 | create(context) { 20 | return { 21 | // eslint-disable-next-line @typescript-eslint/naming-convention 22 | VariableDeclaration(node) { 23 | if (node.kind === 'const') { 24 | node.declarations.forEach((declaration) => { 25 | if ( 26 | declaration.id.type === 'Identifier' && 27 | declaration.init && 28 | declaration.init.type === 'TSAsExpression' && 29 | declaration.init.typeAnnotation.type === 'TSTypeReference' && 30 | declaration.init.typeAnnotation.typeName.name === 'const' 31 | ) { 32 | const name = declaration.id.name; 33 | if (name !== name.toUpperCase()) { 34 | context.report({ 35 | node: declaration.id, 36 | message: 37 | "Constant marked with 'as const' should be in UPPER_CASE", 38 | }); 39 | } 40 | } 41 | }); 42 | } 43 | }, 44 | }; 45 | }, 46 | }; 47 | 48 | const commonConfig = { 49 | plugins: { 50 | '@typescript-eslint': ts.plugin, 51 | unicorn: pluginUnicorn, 52 | cspell: cspellPlugin, 53 | custom: { 54 | rules: { 55 | 'enforce-upper-case-as-const': enforceUpperCaseForAsConstRule, 56 | }, 57 | }, 58 | }, 59 | languageOptions: { 60 | globals: globals.browser, 61 | }, 62 | rules: { 63 | 'no-new-object': 'error', 64 | 'object-shorthand': 'error', 65 | 'prefer-object-spread': 'error', 66 | 'prefer-template': 'error', 67 | 'template-curly-spacing': 'error', 68 | 'default-param-last': 'error', 69 | 'no-new-func': 'error', 70 | 'no-param-reassign': 'error', 71 | 'prefer-arrow-callback': 'error', 72 | 'nonblock-statement-body-position': 'error', 73 | 'no-else-return': 'error', 74 | 'spaced-comment': 'error', 75 | 'no-console': ['error', { allow: ['info', 'clear', 'error', 'warn'] }], 76 | 'no-nested-ternary': 'error', 77 | 'unicorn/filename-case': ['error', { case: 'kebabCase' }], 78 | 'custom/enforce-upper-case-as-const': 'error', 79 | 'cspell/spellchecker': [ 80 | 'error', 81 | { 82 | autoFix: false, 83 | cspell: { 84 | words: [ 85 | 'shadcn', 86 | 'svelte-tel-input', 87 | 'sonner', 88 | 'cmdk', 89 | 'nonblock', 90 | 'bunx', 91 | 'jsrepo', 92 | 'ieedan', 93 | ], 94 | }, 95 | }, 96 | ], 97 | '@typescript-eslint/array-type': ['error', { default: 'array' }], 98 | '@typescript-eslint/no-unused-vars': [ 99 | 'error', 100 | { varsIgnorePattern: '^_|^\\$\\$', argsIgnorePattern: '^_' }, 101 | ], 102 | '@typescript-eslint/consistent-type-imports': [ 103 | 'error', 104 | { prefer: 'type-imports' }, 105 | ], 106 | 'max-params': 'off', 107 | '@typescript-eslint/max-params': 'error', 108 | '@typescript-eslint/prefer-as-const': 'error', 109 | '@typescript-eslint/naming-convention': [ 110 | 'error', 111 | { 112 | selector: 'default', 113 | format: ['camelCase'], 114 | leadingUnderscore: 'allow', 115 | trailingUnderscore: 'forbid', 116 | }, 117 | { 118 | selector: 'typeLike', 119 | format: ['PascalCase'], 120 | leadingUnderscore: 'allow', 121 | trailingUnderscore: 'forbid', 122 | }, 123 | { 124 | selector: 'variable', 125 | modifiers: ['const'], 126 | format: ['UPPER_CASE', 'camelCase', 'PascalCase'], 127 | trailingUnderscore: 'forbid', 128 | leadingUnderscore: 'allow', 129 | }, 130 | { 131 | selector: 'import', 132 | format: ['camelCase', 'PascalCase'], 133 | trailingUnderscore: 'forbid', 134 | }, 135 | { 136 | selector: 'objectLiteralProperty', 137 | format: null, 138 | }, 139 | ], 140 | }, 141 | }; 142 | 143 | /** @type {import('eslint').Linter.FlatConfig[]} */ 144 | export default [ 145 | js.configs.recommended, 146 | ...ts.configs.recommended, 147 | ...svelte.configs['flat/recommended'], 148 | prettier, 149 | commonConfig, 150 | ...svelte.configs['flat/prettier'], 151 | { 152 | languageOptions: { 153 | globals: { 154 | ...globals.browser, 155 | ...globals.node, 156 | }, 157 | }, 158 | }, 159 | { 160 | files: ['**/*.svelte'], 161 | rules: { 162 | 'svelte/no-at-html-tags': 'warn', 163 | }, 164 | languageOptions: { 165 | parserOptions: { 166 | parser: ts.parser, 167 | }, 168 | }, 169 | }, 170 | { 171 | ignores: ['build/', '.svelte-kit/', 'dist/'], 172 | }, 173 | ]; 174 | -------------------------------------------------------------------------------- /jsrepo-manifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "ui", 4 | "blocks": [ 5 | { 6 | "name": "button", 7 | "directory": "src/lib/components/ui/button", 8 | "category": "ui", 9 | "tests": false, 10 | "subdirectory": true, 11 | "files": [ 12 | "button.svelte", 13 | "index.ts" 14 | ], 15 | "localDependencies": [ 16 | "lib/utils" 17 | ], 18 | "dependencies": [], 19 | "devDependencies": [], 20 | "_imports_": { 21 | "$lib/utils.js": "{{lib/utils}}.js" 22 | } 23 | }, 24 | { 25 | "name": "command", 26 | "directory": "src/lib/components/ui/command", 27 | "category": "ui", 28 | "tests": false, 29 | "subdirectory": true, 30 | "files": [ 31 | "command-dialog.svelte", 32 | "command-empty.svelte", 33 | "command-group.svelte", 34 | "command-input.svelte", 35 | "command-item.svelte", 36 | "command-link-item.svelte", 37 | "command-list.svelte", 38 | "command-separator.svelte", 39 | "command-shortcut.svelte", 40 | "command.svelte", 41 | "index.ts" 42 | ], 43 | "localDependencies": [ 44 | "ui/dialog", 45 | "lib/utils" 46 | ], 47 | "dependencies": [], 48 | "devDependencies": [ 49 | "bits-ui@1.0.0-next.35", 50 | "lucide-svelte@^0.460.1" 51 | ], 52 | "_imports_": { 53 | "$lib/components/ui/dialog/index.js": "{{ui/dialog}}/index.js", 54 | "$lib/utils.js": "{{lib/utils}}.js" 55 | } 56 | }, 57 | { 58 | "name": "dialog", 59 | "directory": "src/lib/components/ui/dialog", 60 | "category": "ui", 61 | "tests": false, 62 | "subdirectory": true, 63 | "files": [ 64 | "dialog-content.svelte", 65 | "dialog-description.svelte", 66 | "dialog-footer.svelte", 67 | "dialog-header.svelte", 68 | "dialog-overlay.svelte", 69 | "dialog-portal.svelte", 70 | "dialog-title.svelte", 71 | "index.ts" 72 | ], 73 | "localDependencies": [ 74 | "lib/utils" 75 | ], 76 | "dependencies": [], 77 | "devDependencies": [ 78 | "bits-ui@1.0.0-next.35", 79 | "lucide-svelte@^0.460.1" 80 | ], 81 | "_imports_": { 82 | "$lib/utils.js": "{{lib/utils}}.js" 83 | } 84 | }, 85 | { 86 | "name": "phone-input", 87 | "directory": "src/lib/components/ui/phone-input", 88 | "category": "ui", 89 | "tests": false, 90 | "subdirectory": true, 91 | "files": [ 92 | "country-selector.svelte", 93 | "flag.svelte", 94 | "index.ts", 95 | "phone-input.svelte" 96 | ], 97 | "localDependencies": [ 98 | "ui/popover", 99 | "ui/button", 100 | "ui/command", 101 | "ui/scroll-area", 102 | "lib/utils" 103 | ], 104 | "dependencies": [], 105 | "devDependencies": [ 106 | "lucide-svelte@^0.460.1", 107 | "svelte-tel-input@^3.5.0" 108 | ], 109 | "_imports_": { 110 | "$lib/components/ui/popover": "{{ui/popover}}", 111 | "$lib/components/ui/button": "{{ui/button}}", 112 | "$lib/components/ui/command": "{{ui/command}}", 113 | "$lib/components/ui/scroll-area": "{{ui/scroll-area}}", 114 | "$lib/utils": "{{lib/utils}}" 115 | } 116 | }, 117 | { 118 | "name": "popover", 119 | "directory": "src/lib/components/ui/popover", 120 | "category": "ui", 121 | "tests": false, 122 | "subdirectory": true, 123 | "files": [ 124 | "index.ts", 125 | "popover-content.svelte" 126 | ], 127 | "localDependencies": [ 128 | "lib/utils" 129 | ], 130 | "dependencies": [], 131 | "devDependencies": [ 132 | "bits-ui@1.0.0-next.35" 133 | ], 134 | "_imports_": { 135 | "$lib/utils.js": "{{lib/utils}}.js" 136 | } 137 | }, 138 | { 139 | "name": "scroll-area", 140 | "directory": "src/lib/components/ui/scroll-area", 141 | "category": "ui", 142 | "tests": false, 143 | "subdirectory": true, 144 | "files": [ 145 | "index.ts", 146 | "scroll-area-scrollbar.svelte", 147 | "scroll-area.svelte" 148 | ], 149 | "localDependencies": [ 150 | "lib/utils" 151 | ], 152 | "dependencies": [], 153 | "devDependencies": [ 154 | "bits-ui@1.0.0-next.35" 155 | ], 156 | "_imports_": { 157 | "$lib/utils.js": "{{lib/utils}}.js" 158 | } 159 | } 160 | ] 161 | }, 162 | { 163 | "name": "lib", 164 | "blocks": [ 165 | { 166 | "name": "utils", 167 | "directory": "src/lib", 168 | "category": "lib", 169 | "tests": false, 170 | "subdirectory": false, 171 | "files": [ 172 | "utils.ts" 173 | ], 174 | "localDependencies": [], 175 | "_imports_": {}, 176 | "dependencies": [ 177 | "clsx@^2.1.1", 178 | "tailwind-merge@^2.4.0" 179 | ], 180 | "devDependencies": [] 181 | } 182 | ] 183 | } 184 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-phone-input-svelte", 3 | "version": "0.0.1", 4 | "private": true, 5 | "packageManager": "pnpm@9.12.3", 6 | "scripts": { 7 | "dev": "vite dev", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --check . && eslint .", 13 | "format": "npx changy format && prettier --write .", 14 | "changelog:new": "npx changy add", 15 | "build:registry": "jsrepo build --dirs ./src/lib/components ./src --include-categories ui lib --include-blocks phone-input utils button command popover scroll-area dialog" 16 | }, 17 | "devDependencies": { 18 | "@sveltejs/kit": "^2.8.2", 19 | "@sveltejs/vite-plugin-svelte": "^4.0.1", 20 | "@tailwindcss/typography": "^0.5.14", 21 | "@types/eslint": "^9.6.1", 22 | "@vercel/analytics": "^1.4.1", 23 | "@vercel/speed-insights": "^1.1.0", 24 | "autoprefixer": "^10.4.19", 25 | "bits-ui": "1.0.0-next.35", 26 | "eslint": "^9.15.0", 27 | "eslint-config-prettier": "^9.1.0", 28 | "eslint-plugin-svelte": "^2.45.1", 29 | "globals": "^15.12.0", 30 | "jsrepo": "^1.15.1", 31 | "lucide-svelte": "^0.460.1", 32 | "mode-watcher": "^0.5.0", 33 | "postcss": "^8.4.49", 34 | "prettier": "^3.1.1", 35 | "prettier-plugin-svelte": "^3.3.2", 36 | "prettier-plugin-tailwindcss": "^0.6.9", 37 | "svelte": "^5.2.7", 38 | "svelte-check": "^4.1.0", 39 | "svelte-sonner": "^0.3.28", 40 | "svelte-tel-input": "^3.5.0", 41 | "tailwindcss": "^3.4.15", 42 | "tslib": "^2.4.1", 43 | "typescript": "^5.7.2", 44 | "typescript-eslint": "^8.15.0", 45 | "vite": "^5.4.11" 46 | }, 47 | "type": "module", 48 | "dependencies": { 49 | "@cspell/eslint-plugin": "^8.16.0", 50 | "@fontsource/geist-mono": "^5.0.3", 51 | "@fontsource/geist-sans": "^5.0.3", 52 | "@sveltejs/adapter-vercel": "^5.4.8", 53 | "clsx": "^2.1.1", 54 | "eslint-plugin-unicorn": "^56.0.1", 55 | "libphonenumber-js": "^1.11.15", 56 | "marked": "^15.0.2", 57 | "sveltekit-superforms": "^2.20.1", 58 | "tailwind-merge": "^2.4.0", 59 | "tailwind-variants": "^0.3.0", 60 | "zod": "^3.23.8" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* For components that need horizontal scrolling */ 6 | .scrollbar-hide { 7 | -ms-overflow-style: none; /* Internet Explorer and Edge */ 8 | scrollbar-width: none; /* Firefox */ 9 | } 10 | 11 | @layer base { 12 | :root { 13 | --background: 0 0% 100%; 14 | --foreground: 240 10% 3.9%; 15 | 16 | --muted: 240 4.8% 95.9%; 17 | --muted-foreground: 240 3.8% 46.1%; 18 | 19 | --popover: 0 0% 100%; 20 | --popover-foreground: 240 10% 3.9%; 21 | 22 | --card: 0 0% 100%; 23 | --card-foreground: 240 10% 3.9%; 24 | 25 | --border: 240 5.9% 90%; 26 | --input: 240 5.9% 90%; 27 | 28 | --primary: 240 5.9% 10%; 29 | --primary-foreground: 0 0% 98%; 30 | 31 | --secondary: 240 4.8% 95.9%; 32 | --secondary-foreground: 240 5.9% 10%; 33 | 34 | --accent: 240 4.8% 95.9%; 35 | --accent-foreground: 240 5.9% 10%; 36 | 37 | --destructive: 0 72.2% 50.6%; 38 | --destructive-foreground: 0 0% 98%; 39 | 40 | --ring: 240 10% 3.9%; 41 | 42 | --radius: 0.5rem; 43 | } 44 | 45 | .dark { 46 | --background: 240 10% 3.9%; 47 | --foreground: 0 0% 98%; 48 | 49 | --muted: 240 3.7% 15.9%; 50 | --muted-foreground: 240 5% 64.9%; 51 | 52 | --popover: 240 10% 3.9%; 53 | --popover-foreground: 0 0% 98%; 54 | 55 | --card: 240 10% 3.9%; 56 | --card-foreground: 0 0% 98%; 57 | 58 | --border: 240 3.7% 15.9%; 59 | --input: 240 3.7% 15.9%; 60 | 61 | --primary: 0 0% 98%; 62 | --primary-foreground: 240 5.9% 10%; 63 | 64 | --secondary: 240 3.7% 15.9%; 65 | --secondary-foreground: 0 0% 98%; 66 | 67 | --accent: 240 3.7% 15.9%; 68 | --accent-foreground: 0 0% 98%; 69 | 70 | --destructive: 0 62.8% 30.6%; 71 | --destructive-foreground: 0 0% 98%; 72 | 73 | --ring: 240 4.9% 83.9%; 74 | } 75 | } 76 | 77 | @layer base { 78 | * { 79 | @apply border-border; 80 | } 81 | body { 82 | @apply bg-background text-foreground; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/hooks.client.ts: -------------------------------------------------------------------------------- 1 | import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit'; 2 | import { dev } from '$app/environment'; 3 | import { inject } from '@vercel/analytics'; 4 | 5 | inject({ mode: dev ? 'development' : 'production' }); 6 | injectSpeedInsights(); 7 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 | 60 | 61 | {#if href} 62 | 68 | {@render children?.()} 69 | 70 | {:else} 71 | 79 | {/if} 80 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root, { 2 | type ButtonProps, 3 | type ButtonSize, 4 | type ButtonVariant, 5 | buttonVariants, 6 | } from './button.svelte'; 7 | 8 | export { 9 | Root, 10 | type ButtonProps as Props, 11 | // 12 | Root as Button, 13 | buttonVariants, 14 | type ButtonProps, 15 | type ButtonSize, 16 | type ButtonVariant, 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-dialog.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-empty.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-group.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | {#if heading} 22 | 25 | {heading} 26 | 27 | {/if} 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-input.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 | 25 |
26 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-item.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-link-item.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-list.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-separator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/command.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/command/index.ts: -------------------------------------------------------------------------------- 1 | import { Command as CommandPrimitive } from 'bits-ui'; 2 | 3 | import Root from './command.svelte'; 4 | import Dialog from './command-dialog.svelte'; 5 | import Empty from './command-empty.svelte'; 6 | import Group from './command-group.svelte'; 7 | import Item from './command-item.svelte'; 8 | import Input from './command-input.svelte'; 9 | import List from './command-list.svelte'; 10 | import Separator from './command-separator.svelte'; 11 | import Shortcut from './command-shortcut.svelte'; 12 | import LinkItem from './command-link-item.svelte'; 13 | 14 | const Loading = CommandPrimitive.Loading; 15 | 16 | export { 17 | Root, 18 | Dialog, 19 | Empty, 20 | Group, 21 | Item, 22 | LinkItem, 23 | Input, 24 | List, 25 | Separator, 26 | Shortcut, 27 | Loading, 28 | // 29 | Root as Command, 30 | Dialog as CommandDialog, 31 | Empty as CommandEmpty, 32 | Group as CommandGroup, 33 | Item as CommandItem, 34 | LinkItem as CommandLinkItem, 35 | Input as CommandInput, 36 | List as CommandList, 37 | Separator as CommandSeparator, 38 | Shortcut as CommandShortcut, 39 | Loading as CommandLoading, 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-content.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 31 | {@render children?.()} 32 | 35 | 36 | Close 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
22 | {@render children?.()} 23 |
24 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-overlay.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-portal.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | {@render children?.()} 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as DialogPrimitive } from 'bits-ui'; 2 | 3 | import Title from './dialog-title.svelte'; 4 | import Footer from './dialog-footer.svelte'; 5 | import Header from './dialog-header.svelte'; 6 | import Overlay from './dialog-overlay.svelte'; 7 | import Content from './dialog-content.svelte'; 8 | import Description from './dialog-description.svelte'; 9 | 10 | const Root = DialogPrimitive.Root; 11 | const Trigger = DialogPrimitive.Trigger; 12 | const Close = DialogPrimitive.Close; 13 | const Portal = DialogPrimitive.Portal; 14 | 15 | export { 16 | Root, 17 | Title, 18 | Portal, 19 | Footer, 20 | Header, 21 | Trigger, 22 | Overlay, 23 | Content, 24 | Description, 25 | Close, 26 | // 27 | Root as Dialog, 28 | Title as DialogTitle, 29 | Portal as DialogPortal, 30 | Footer as DialogFooter, 31 | Header as DialogHeader, 32 | Trigger as DialogTrigger, 33 | Overlay as DialogOverlay, 34 | Content as DialogContent, 35 | Description as DialogDescription, 36 | Close as DialogClose, 37 | }; 38 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | {#snippet children({ checked })} 29 | 30 | {#if checked === 'indeterminate'} 31 | 32 | {:else} 33 | 34 | {/if} 35 | 36 | {@render childrenProp?.({ checked })} 37 | {/snippet} 38 | 39 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
22 | {@render children?.()} 23 |
24 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | {@render children?.()} 17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | {#snippet children({ checked })} 26 | 27 | {#if checked} 28 | 29 | {/if} 30 | 31 | {@render childrenProp?.({ checked })} 32 | {/snippet} 33 | 34 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | {@render children?.()} 20 | 21 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | {@render children?.()} 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/index.ts: -------------------------------------------------------------------------------- 1 | import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; 2 | import CheckboxItem from './dropdown-menu-checkbox-item.svelte'; 3 | import Content from './dropdown-menu-content.svelte'; 4 | import GroupHeading from './dropdown-menu-group-heading.svelte'; 5 | import Item from './dropdown-menu-item.svelte'; 6 | import Label from './dropdown-menu-label.svelte'; 7 | import RadioItem from './dropdown-menu-radio-item.svelte'; 8 | import Separator from './dropdown-menu-separator.svelte'; 9 | import Shortcut from './dropdown-menu-shortcut.svelte'; 10 | import SubContent from './dropdown-menu-sub-content.svelte'; 11 | import SubTrigger from './dropdown-menu-sub-trigger.svelte'; 12 | 13 | const Sub = DropdownMenuPrimitive.Sub; 14 | const Root = DropdownMenuPrimitive.Root; 15 | const Trigger = DropdownMenuPrimitive.Trigger; 16 | const Group = DropdownMenuPrimitive.Group; 17 | const RadioGroup = DropdownMenuPrimitive.RadioGroup; 18 | 19 | export { 20 | CheckboxItem, 21 | Content, 22 | Root as DropdownMenu, 23 | CheckboxItem as DropdownMenuCheckboxItem, 24 | Content as DropdownMenuContent, 25 | Group as DropdownMenuGroup, 26 | GroupHeading as DropdownMenuGroupHeading, 27 | Item as DropdownMenuItem, 28 | Label as DropdownMenuLabel, 29 | RadioGroup as DropdownMenuRadioGroup, 30 | RadioItem as DropdownMenuRadioItem, 31 | Separator as DropdownMenuSeparator, 32 | Shortcut as DropdownMenuShortcut, 33 | Sub as DropdownMenuSub, 34 | SubContent as DropdownMenuSubContent, 35 | SubTrigger as DropdownMenuSubTrigger, 36 | Trigger as DropdownMenuTrigger, 37 | Group, 38 | GroupHeading, 39 | Item, 40 | Label, 41 | RadioGroup, 42 | RadioItem, 43 | Root, 44 | Separator, 45 | Shortcut, 46 | Sub, 47 | SubContent, 48 | SubTrigger, 49 | Trigger, 50 | }; 51 | -------------------------------------------------------------------------------- /src/lib/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './label.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Label, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/label/label.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/link/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './link.svelte'; 2 | 3 | export default Root; 4 | -------------------------------------------------------------------------------- /src/lib/components/ui/link/link.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | {@render children?.()} 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/phone-input/country-selector.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | 45 | {#snippet child({ props })} 46 | 61 | {/snippet} 62 | 63 | 64 | 65 | 66 | 67 | 68 | No country found. 69 | 70 | {#each countries.sort(order) as country} 71 | selectCountry(country)} 75 | > 76 | 77 | {country.name} 78 | 79 | +{country.dialCode} 80 | 81 |
82 | {#if country.iso2 == selected} 83 | 84 | {/if} 85 |
86 |
87 | {/each} 88 |
89 |
90 |
91 |
92 |
93 |
94 | -------------------------------------------------------------------------------- /src/lib/components/ui/phone-input/flag.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | {#if country} 13 | 17 | {/if} 18 | 19 | -------------------------------------------------------------------------------- /src/lib/components/ui/phone-input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './phone-input.svelte'; 2 | import type { 3 | Country, 4 | CountryCode, 5 | DetailedValue, 6 | E164Number, 7 | TelInputOptions, 8 | } from 'svelte-tel-input/types'; 9 | 10 | export type Props = { 11 | country?: CountryCode | null; 12 | defaultCountry?: CountryCode | null; 13 | el?: HTMLInputElement; 14 | name?: string; 15 | placeholder?: string; 16 | disabled?: boolean; 17 | readonly?: boolean; 18 | class?: string; 19 | value: E164Number | null; 20 | valid?: boolean; 21 | detailedValue?: Partial | null; 22 | options?: TelInputOptions; 23 | order?: ((a: Country, b: Country) => number) | undefined; 24 | }; 25 | 26 | export const defaultOptions: TelInputOptions = { 27 | spaces: true, 28 | autoPlaceholder: false, 29 | format: 'international', 30 | }; 31 | 32 | export default Root; 33 | -------------------------------------------------------------------------------- /src/lib/components/ui/phone-input/phone-input.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 |
37 | 43 | 61 |
62 | -------------------------------------------------------------------------------- /src/lib/components/ui/popover/index.ts: -------------------------------------------------------------------------------- 1 | import { Popover as PopoverPrimitive } from 'bits-ui'; 2 | import Content from './popover-content.svelte'; 3 | const Root = PopoverPrimitive.Root; 4 | const Trigger = PopoverPrimitive.Trigger; 5 | const Close = PopoverPrimitive.Close; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Trigger, 11 | Close, 12 | // 13 | Root as Popover, 14 | Content as PopoverContent, 15 | Trigger as PopoverTrigger, 16 | Close as PopoverClose, 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/popover/popover-content.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 25 | 26 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | import Scrollbar from './scroll-area-scrollbar.svelte'; 2 | import Root from './scroll-area.svelte'; 3 | 4 | export { 5 | Root, 6 | Scrollbar, 7 | // ... 8 | Root as ScrollArea, 9 | Scrollbar as ScrollAreaScrollbar, 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | {@render children?.()} 31 | 37 | 38 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/scroll-area.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | 30 | {@render children?.()} 31 | 32 | {#if orientation === 'vertical' || orientation === 'both'} 33 | 34 | {/if} 35 | {#if orientation === 'horizontal' || orientation === 'both'} 36 | 37 | {/if} 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './separator.svelte'; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/copy-button.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | {#if typeof code == 'string'} 48 | 56 | {:else if code != undefined} 57 | 58 | 59 | {#snippet child({ props })} 60 | 68 | {/snippet} 69 | 70 | 71 | {#each code as { name, code: snippet }} 72 | copy(snippet)}> 73 | {name} 74 | 75 | {/each} 76 | 77 | 78 | {/if} 79 | -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/copy-icon.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | {#if !copied} 13 |
14 | 15 |
16 | {:else} 17 |
18 | 19 |
20 | {/if} 21 | -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/index.ts: -------------------------------------------------------------------------------- 1 | import { context } from '$lib/ts/context-provider'; 2 | import Root from './snippet.svelte'; 3 | import CopyButton from './copy-button.svelte'; 4 | 5 | type Context = { 6 | code: string; 7 | }; 8 | 9 | export const rootContext = context('root-context'); 10 | 11 | export { Root, CopyButton }; 12 | -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/snippet.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
25 |
{code}
30 | {#if children} 31 | {@render children()} 32 | {:else if showCopy} 33 | 34 | {/if} 35 |
36 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from './sonner.svelte'; 2 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | import { Tabs as TabsPrimitive } from 'bits-ui'; 2 | import Content from './tabs-content.svelte'; 3 | import List from './tabs-list.svelte'; 4 | import Trigger from './tabs-trigger.svelte'; 5 | 6 | const Root = TabsPrimitive.Root; 7 | 8 | export { 9 | Root, 10 | Content, 11 | List, 12 | Trigger, 13 | // 14 | Root as Tabs, 15 | Content as TabsContent, 16 | List as TabsList, 17 | Trigger as TabsTrigger, 18 | }; 19 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-list.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-trigger.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /src/lib/components/ui/theme-selector/index.ts: -------------------------------------------------------------------------------- 1 | import Root from './theme-selector.svelte'; 2 | 3 | export default Root; 4 | -------------------------------------------------------------------------------- /src/lib/components/ui/theme-selector/theme-selector.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | {#snippet child({ props })} 13 | 22 | {/snippet} 23 | 24 | 25 | setMode('light')}> 26 | Light 27 | 28 | setMode('dark')}>Dark 29 | resetMode()}>System 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/lib/ts/context-provider.ts: -------------------------------------------------------------------------------- 1 | import { getContext, setContext } from 'svelte'; 2 | import { writable, type Writable } from 'svelte/store'; 3 | 4 | export const context = (key: string) => { 5 | return { 6 | init: (value: T) => setContext(key, writable(value)), 7 | get: (): Writable => getContext(key), 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 |
26 |
27 | {#if $page.url.pathname != '/'} 28 | 31 | {/if} 32 |
33 | 34 |
35 | {@render children?.()} 36 |
39 |
42 |

43 | Crafted by Omer Alpi. 44 | Ported to Svelte 48 | by Aidan Bleser. 49 |

50 |
51 | GitHub 54 | Changelog 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { fail, message, superValidate } from 'sveltekit-superforms'; 2 | import { formSchema } from './+page.svelte'; 3 | import { zod } from 'sveltekit-superforms/adapters'; 4 | 5 | export async function load() { 6 | const form = await superValidate(zod(formSchema)); 7 | 8 | return { form }; 9 | } 10 | 11 | export const actions = { 12 | default: async ({ request }) => { 13 | const form = await superValidate(request, zod(formSchema)); 14 | 15 | if (!form.valid) return fail(400, { regForm: form }); 16 | 17 | return message(form, { text: 'Success!' }); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 67 | 68 | 69 | Shadcn Phone Input 70 | 71 | 72 |
73 |
76 |
79 |
82 |

83 | Shadcn Phone Input Svelte 84 |

85 |

86 | An implementation of a Phone Input component built on top of Shadcn 87 | UI's input component. 88 |

89 |
90 | 91 | 97 |
98 |
99 | 100 |
105 |
106 | 109 | 115 | {#if showInvalid} 116 | Invalid Phone Number 117 | {/if} 118 |
119 |
120 | {#if browser} 121 | 122 | {/if} 123 | 124 |
125 |
126 |
127 |
128 |
129 |

Setup

130 | 131 |
132 |
133 |

134 | Install shadcn-svelte via CLI 135 |

136 |

137 | Run the shadcn-svelte init command to setup your project: 140 |

141 | 142 | 150 | 151 |
152 |
153 |

154 | Install necessary components 155 |

156 |

157 | Run the shadcn-svelte add command to add the necessary components to your project: 160 |

161 | 167 | 187 | 188 |
189 |
190 |
191 |

192 | Add Component 193 |

194 | 195 |
196 |

Automatic

197 | 200 | 220 | 221 |

or

222 |

Manual

223 |
224 |
225 |

226 | Install Svelte Tel Input 227 |

228 | 229 | 237 | 238 |
239 |
240 |

Copy the code

241 |

242 | You can find the code here. Or copy it below. 246 |

247 | `src/lib/components/ui/phone-input` 248 | 249 | 250 | 251 | phone-input.svelte 252 | 253 | 254 | country-selector.svelte 255 | 256 | 257 | flag.svelte 258 | 259 | 260 | index.ts 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 |
277 |
278 |
279 |
280 |

281 | Examples 282 |

283 | 284 |
285 | 286 | 287 | 288 | 289 | 294 | 295 | 296 | 300 | 301 | 302 | { 306 | if (a.iso2 == 'US') return -1; 307 | if (b.iso2 == 'US') return 1; 308 | if (a.iso2 == 'CN') return -1; 309 | if (b.iso2 == 'CN') return 1; 310 | 311 | return a.name.localeCompare(b.name); 312 | }} 313 | /> 314 | 315 | 316 | 321 |
322 | 323 | National: {detailedValue?.nationalNumber 324 | ? detailedValue.nationalNumber 325 | : ''} 326 | 327 | 328 | International: {detailedValue?.formatInternational 329 | ? detailedValue.formatInternational 330 | : ''} 331 | 332 | 333 | Country Code: {detailedValue?.countryCode 334 | ? detailedValue.countryCode 335 | : ''} 336 | 337 | 338 | Dial Code: {detailedValue?.countryCallingCode 339 | ? detailedValue.countryCallingCode 340 | : ''} 341 | 342 |
343 |
344 |
345 |
346 |
347 |
348 | -------------------------------------------------------------------------------- /src/routes/changelog/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked'; 2 | 3 | export async function load() { 4 | const changelogPath = 5 | 'https://raw.githubusercontent.com/ieedan/shadcn-phone-input-svelte/main/CHANGELOG.md'; 6 | 7 | const res = await fetch(changelogPath); 8 | 9 | return { 10 | content: marked.parse(await res.text()), 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/routes/changelog/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
9 |
12 |
13 |

Changelog

14 |

15 | Latest updates and announcements. 16 |

17 |
18 |
19 | {@html data.content} 20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/routes/example-container.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | {#if title} 21 |

{title}

22 | {/if} 23 |
30 | {@render children?.()} 31 |
32 |
33 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ieedan/shadcn-phone-input-svelte/20bbb127af9a621ff0e2e720092a3f4d8cf70661/static/favicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-vercel'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | adapter: adapter({ 12 | // See docs https://kit.svelte.dev/docs/adapter-vercel 13 | }), 14 | }, 15 | }; 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | darkMode: ['class'], 5 | content: ['./src/**/*.{html,js,svelte,ts}'], 6 | safelist: ['dark'], 7 | theme: { 8 | container: { 9 | center: true, 10 | padding: '2rem', 11 | screens: { 12 | '2xl': '1400px', 13 | }, 14 | }, 15 | extend: { 16 | colors: { 17 | border: 'hsl(var(--border) / )', 18 | input: 'hsl(var(--input) / )', 19 | ring: 'hsl(var(--ring) / )', 20 | background: 'hsl(var(--background) / )', 21 | foreground: 'hsl(var(--foreground) / )', 22 | primary: { 23 | DEFAULT: 'hsl(var(--primary) / )', 24 | foreground: 'hsl(var(--primary-foreground) / )', 25 | }, 26 | secondary: { 27 | DEFAULT: 'hsl(var(--secondary) / )', 28 | foreground: 'hsl(var(--secondary-foreground) / )', 29 | }, 30 | destructive: { 31 | DEFAULT: 'hsl(var(--destructive) / )', 32 | foreground: 'hsl(var(--destructive-foreground) / )', 33 | }, 34 | muted: { 35 | DEFAULT: 'hsl(var(--muted) / )', 36 | foreground: 'hsl(var(--muted-foreground) / )', 37 | }, 38 | accent: { 39 | DEFAULT: 'hsl(var(--accent) / )', 40 | foreground: 'hsl(var(--accent-foreground) / )', 41 | }, 42 | popover: { 43 | DEFAULT: 'hsl(var(--popover) / )', 44 | foreground: 'hsl(var(--popover-foreground) / )', 45 | }, 46 | card: { 47 | DEFAULT: 'hsl(var(--card) / )', 48 | foreground: 'hsl(var(--card-foreground) / )', 49 | }, 50 | }, 51 | borderRadius: { 52 | lg: 'var(--radius)', 53 | md: 'calc(var(--radius) - 2px)', 54 | sm: 'calc(var(--radius) - 4px)', 55 | }, 56 | fontFamily: { 57 | serif: ['Geist Mono', 'Monospace'], 58 | sans: ['Geist Sans', 'sans-serif'], 59 | }, 60 | }, 61 | }, 62 | // eslint-disable-next-line @typescript-eslint/no-require-imports 63 | plugins: [require('@tailwindcss/typography')], 64 | }; 65 | 66 | export default config; 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }); 7 | --------------------------------------------------------------------------------