├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── bun.lockb ├── components.json ├── package.json ├── postcss.config.js ├── src ├── app.css ├── app.d.ts ├── app.html ├── lib │ ├── components │ │ ├── theme │ │ │ └── toggle-mode │ │ │ │ ├── index.ts │ │ │ │ └── toggle-mode.svelte │ │ └── ui │ │ │ ├── accordion │ │ │ ├── accordion-content.svelte │ │ │ ├── accordion-item.svelte │ │ │ ├── accordion-trigger.svelte │ │ │ └── index.ts │ │ │ ├── button │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ │ ├── calendar │ │ │ ├── calendar-cell.svelte │ │ │ ├── calendar-day.svelte │ │ │ ├── calendar-grid-body.svelte │ │ │ ├── calendar-grid-head.svelte │ │ │ ├── calendar-grid-row.svelte │ │ │ ├── calendar-grid.svelte │ │ │ ├── calendar-head-cell.svelte │ │ │ ├── calendar-header.svelte │ │ │ ├── calendar-heading.svelte │ │ │ ├── calendar-months.svelte │ │ │ ├── calendar-next-button.svelte │ │ │ ├── calendar-prev-button.svelte │ │ │ ├── calendar.svelte │ │ │ └── index.ts │ │ │ ├── form │ │ │ ├── form-button.svelte │ │ │ ├── form-description.svelte │ │ │ ├── form-element-field.svelte │ │ │ ├── form-field-errors.svelte │ │ │ ├── form-field.svelte │ │ │ ├── form-fieldset.svelte │ │ │ ├── form-label.svelte │ │ │ ├── form-legend.svelte │ │ │ └── index.ts │ │ │ ├── input │ │ │ ├── index.ts │ │ │ └── input.svelte │ │ │ ├── label │ │ │ ├── index.ts │ │ │ └── label.svelte │ │ │ ├── popover │ │ │ ├── index.ts │ │ │ └── popover-content.svelte │ │ │ ├── select │ │ │ ├── index.ts │ │ │ ├── select-content.svelte │ │ │ ├── select-group-heading.svelte │ │ │ ├── select-item.svelte │ │ │ ├── select-scroll-down-button.svelte │ │ │ ├── select-scroll-up-button.svelte │ │ │ ├── select-separator.svelte │ │ │ └── select-trigger.svelte │ │ │ ├── separator │ │ │ ├── index.ts │ │ │ └── separator.svelte │ │ │ ├── snippet │ │ │ ├── index.ts │ │ │ ├── pre.svelte │ │ │ └── snippet.svelte │ │ │ ├── sonner │ │ │ ├── index.ts │ │ │ └── sonner.svelte │ │ │ └── tabs │ │ │ ├── index.ts │ │ │ ├── tabs-content.svelte │ │ │ ├── tabs-list.svelte │ │ │ └── tabs-trigger.svelte │ ├── hooks │ │ └── use-mouse-move.ts │ ├── index.ts │ ├── snippets-content.ts │ ├── snippets │ │ ├── shadcn-svelte-4 │ │ │ ├── time-picker-input.svelte │ │ │ ├── time-picker-utils.ts │ │ │ └── time-picker.svelte │ │ ├── shadcn-svelte-runes │ │ │ ├── date-time-picker-form.svelte │ │ │ ├── date-time-picker.svelte │ │ │ ├── time-period-select.svelte │ │ │ ├── time-picker-12h.svelte │ │ │ ├── time-picker-input.svelte │ │ │ ├── time-picker-utils.ts │ │ │ └── time-picker.svelte │ │ ├── svelte-4 │ │ │ ├── cn.ts │ │ │ ├── time-picker-input.svelte │ │ │ ├── time-picker-utils.ts │ │ │ └── time-picker.svelte │ │ └── svelte-runes │ │ │ ├── cn.ts │ │ │ ├── time-picker-input.svelte │ │ │ ├── time-picker-utils.ts │ │ │ └── time-picker.svelte │ ├── types.ts │ └── utils.ts └── routes │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ ├── _background.svelte │ └── _og-image.svelte ├── static └── favicon.png ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .netlify 7 | .wrangler 8 | /.svelte-kit 9 | /build 10 | 11 | # OS 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Env 16 | .env 17 | .env.* 18 | !.env.example 19 | !.env.test 20 | 21 | # Vite 22 | vite.config.js.timestamp-* 23 | vite.config.ts.timestamp-* 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.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": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], 7 | "overrides": [ 8 | { 9 | "files": "*.svelte", 10 | "options": { 11 | "parser": "svelte" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Yurii Hulyk 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 | > [!TIP] 2 | > Now time picker works with Svelte 4 and Svelte 5 as shadcn or headless! 3 | 4 | # Time Picker Component for Svelte 5 | 6 | This project is a Svelte adaptation of the [TimePickerInput](https://time.openstatus.dev/) component, originally built with React and Shadcn UI. The Svelte version utilizes [svelte-shadcn](https://next.shadcn-svelte.com/docs/installation/sveltekit) components to provide a customizable and accessible time picker for your applications. 7 | 8 | ## Features 9 | 10 | - **Keyboard Navigation**: Supports arrow key navigation for time selection. 11 | - **Date Formatting**: Formats date values for display. 12 | - **Mobile Optimization**: Enhances mobile keyboard interactions for time input. 13 | 14 | ## Demo 15 | - **Docs/Demo**: [link](https://time-picker.nouro.app/) 16 | - **REPL playground**: [link](https://www.sveltelab.dev/zjcmgpaa2tzlylx) 17 | 18 | ## Installation 19 | 20 | **Note**: This component currently works only with SvelteKit projects. Attempting to set up shadcn-svelte on a Svelte project without SvelteKit may result in issues. (But you can use it as headless) 21 | 22 | > [!TIP] 23 | > Time picker can be used without shadcn, but you should a little change code (few lines) 24 | 25 | Before integrating the time picker component, ensure your project includes the following dependencies: 26 | 27 | - [bits-ui](https://www.npmjs.com/package/bits-ui) 28 | - [@internationalized/date](https://www.npmjs.com/package/@internationalized/date) 29 | 30 | Additionally, install the necessary svelte-shadcn components: 31 | 32 | - Input (required) 33 | - Label (optional, for [time-picker.svelte](./src/lib/snippets/time-picker.svelte)) 34 | - Select (optional, for period selection) 35 | - Calendar (optional, for date and time selection) 36 | - Popover (optional, for date and time selection) 37 | - Button (optional, for date and time selection) 38 | 39 | ### Steps to Install (shadcn/ui) 40 | 41 | 1. **Initialize shadcn-svelte in your SvelteKit project**: 42 | 43 | ```bash 44 | npx shadcn-svelte@latest init 45 | ``` 46 | 47 | Follow the prompts to configure your project. Ensure you have TypeScript and Tailwind CSS set up, as the command assumes a SvelteKit project with these configurations. 48 | 49 | 2. **Add the required components**: 50 | 51 | ```bash 52 | npx shadcn-svelte@latest add input 53 | 54 | # Optional 55 | npx shadcn-svelte@latest add select label calendar popover 56 | ``` 57 | 58 | This command will add the specified components to your project. 59 | 60 | 3. **Install additional dependencies**: 61 | 62 | ```bash 63 | npm install @internationalized/date 64 | ``` 65 | 66 | Ensure these packages are added to your `package.json` and installed in your project. 67 | 68 | 4. **Copy main snippets** 69 | 70 | Copy [`time-picker-input.svelte`](./src/lib/snippets/shadcn-svelte-runes/time-picker-input.svelte) and [`time-picker-utils.ts`](./src/lib/snippets/shadcn-svelte-runes/time-picker-utils.ts) into your project. (For example into `$lib/components/ui/time-picker`) 71 | 72 | ### Steps to Install (Headless) 73 | 74 | 1. **Install dependencies**: 75 | 76 | ```bash 77 | npm install @internationalized/date 78 | ``` 79 | 80 | Ensure these packages are added to your `package.json` and installed in your project. 81 | 82 | 2. **Copy main snippets** 83 | 84 | Copy [`time-picker-input.svelte`](./src/lib/snippets/svelte-runes/time-picker-input.svelte) and [`time-picker-utils.ts`](./src/lib/snippets/svelte-runes/time-picker-utils.ts) into your project. (For example into `$lib/components/ui/time-picker`) 85 | 86 | ## Usage 87 | 88 | After installation, you can [copy snippets](./src/lib/snippets/) and use the time picker components in your Svelte files. (can be without shadcn) 89 | 90 | Customize the component as needed to fit your application's requirements. 91 | 92 | ## Acknowledgments 93 | 94 | This component is inspired by the original [TimePicker](https://time.openstatus.dev/) by OpenStatus and utilizes components from [svelte-shadcn](https://next.shadcn-svelte.com/docs/installation/sveltekit). 95 | Special thanks to the contributors of these projects for their excellent work. 96 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1bye/time-picker-svelte/7016b82b8f42bdc48cdf10ff1164ee31d5024e84/bun.lockb -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://next.shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src\\app.css", 7 | "baseColor": "slate" 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 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "time-picker-svelte", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 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 | "format": "prettier --write .", 13 | "lint": "prettier --check ." 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^3.0.0", 17 | "@sveltejs/adapter-vercel": "^5.5.2", 18 | "@sveltejs/kit": "^2.0.0", 19 | "@sveltejs/vite-plugin-svelte": "^4.0.0", 20 | "autoprefixer": "^10.4.20", 21 | "bits-ui": "^1.0.0-next.74", 22 | "clsx": "^2.1.1", 23 | "formsnap": "^2.0.0", 24 | "lucide-svelte": "^0.469.0", 25 | "mode-watcher": "^0.5.0", 26 | "prettier": "^3.3.2", 27 | "prettier-plugin-svelte": "^3.2.6", 28 | "prettier-plugin-tailwindcss": "^0.6.5", 29 | "svelte": "^5.0.0", 30 | "svelte-check": "^4.0.0", 31 | "tailwind-merge": "^2.6.0", 32 | "tailwind-variants": "^0.3.0", 33 | "tailwindcss": "^3.4.9", 34 | "tailwindcss-animate": "^1.0.7", 35 | "typescript": "^5.0.0", 36 | "vite": "^5.4.11" 37 | }, 38 | "dependencies": { 39 | "@internationalized/date": "^3.6.0", 40 | "highlight.js": "^11.11.1", 41 | "highlight.svelte": "^0.1.2", 42 | "svelte-meta-tags": "^4.0.4", 43 | "svelte-sonner": "^0.3.28", 44 | "sveltekit-superforms": "^2.22.1", 45 | "zod": "^3.24.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap'); 6 | 7 | @layer base { 8 | :root { 9 | --background: 0 0% 100%; 10 | --foreground: 222.2 84% 4.9%; 11 | --muted: 210 40% 96.1%; 12 | --muted-foreground: 215.4 16.3% 46.9%; 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | --card: 0 0% 100%; 16 | --card-foreground: 222.2 84% 4.9%; 17 | --border: 214.3 31.8% 91.4%; 18 | --input: 214.3 31.8% 91.4%; 19 | --primary: 222.2 47.4% 11.2%; 20 | --primary-foreground: 210 40% 98%; 21 | --secondary: 210 40% 96.1%; 22 | --secondary-foreground: 222.2 47.4% 11.2%; 23 | --accent: 210 40% 96.1%; 24 | --accent-foreground: 222.2 47.4% 11.2%; 25 | --destructive: 0 72.2% 50.6%; 26 | --destructive-foreground: 210 40% 98%; 27 | --ring: 222.2 84% 4.9%; 28 | --radius: 0.5rem; 29 | --sidebar-background: 0 0% 98%; 30 | --sidebar-foreground: 240 5.3% 26.1%; 31 | --sidebar-primary: 240 5.9% 10%; 32 | --sidebar-primary-foreground: 0 0% 98%; 33 | --sidebar-accent: 240 4.8% 95.9%; 34 | --sidebar-accent-foreground: 240 5.9% 10%; 35 | --sidebar-border: 220 13% 91%; 36 | --sidebar-ring: 217.2 91.2% 59.8%; 37 | } 38 | 39 | .dark { 40 | --background: 222.2 84% 4.9%; 41 | --foreground: 210 40% 98%; 42 | --muted: 217.2 32.6% 17.5%; 43 | --muted-foreground: 215 20.2% 65.1%; 44 | --popover: 222.2 84% 4.9%; 45 | --popover-foreground: 210 40% 98%; 46 | --card: 222.2 84% 4.9%; 47 | --card-foreground: 210 40% 98%; 48 | --border: 217.2 32.6% 17.5%; 49 | --input: 217.2 32.6% 17.5%; 50 | --primary: 210 40% 98%; 51 | --primary-foreground: 222.2 47.4% 11.2%; 52 | --secondary: 217.2 32.6% 17.5%; 53 | --secondary-foreground: 210 40% 98%; 54 | --accent: 217.2 32.6% 17.5%; 55 | --accent-foreground: 210 40% 98%; 56 | --destructive: 0 62.8% 30.6%; 57 | --destructive-foreground: 210 40% 98%; 58 | --ring: 212.7 26.8% 83.9%; 59 | --sidebar-background: 240 5.9% 10%; 60 | --sidebar-foreground: 240 4.8% 95.9%; 61 | --sidebar-primary: 224.3 76.3% 48%; 62 | --sidebar-primary-foreground: 0 0% 100%; 63 | --sidebar-accent: 240 3.7% 15.9%; 64 | --sidebar-accent-foreground: 240 4.8% 95.9%; 65 | --sidebar-border: 240 3.7% 15.9%; 66 | --sidebar-ring: 217.2 91.2% 59.8%; 67 | } 68 | } 69 | 70 | @layer base { 71 | * { 72 | @apply border-border; 73 | } 74 | body { 75 | @apply bg-background text-foreground; 76 | } 77 | } -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://svelte.dev/docs/kit/types#app.d.ts 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/lib/components/theme/toggle-mode/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ModeToggle } from "./toggle-mode.svelte"; -------------------------------------------------------------------------------- /src/lib/components/theme/toggle-mode/toggle-mode.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/accordion/accordion-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 |
22 | {@render children?.()} 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/lib/components/ui/accordion/accordion-item.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/ui/accordion/accordion-trigger.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | svg]:rotate-180", 22 | className 23 | )} 24 | {...restProps} 25 | > 26 | {@render children?.()} 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/lib/components/ui/accordion/index.ts: -------------------------------------------------------------------------------- 1 | import { Accordion as AccordionPrimitive } from "bits-ui"; 2 | import Content from "./accordion-content.svelte"; 3 | import Item from "./accordion-item.svelte"; 4 | import Trigger from "./accordion-trigger.svelte"; 5 | const Root = AccordionPrimitive.Root; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Item, 11 | Trigger, 12 | // 13 | Root as Accordion, 14 | Content as AccordionContent, 15 | Item as AccordionItem, 16 | Trigger as AccordionTrigger, 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 | 55 | 56 | {#if href} 57 | 63 | {@render children?.()} 64 | 65 | {:else} 66 | 74 | {/if} 75 | -------------------------------------------------------------------------------- /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/calendar/calendar-cell.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-day.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-grid-body.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-grid-head.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-grid-row.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-grid.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-head-cell.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-header.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-heading.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-months.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
19 | {@render children?.()} 20 |
21 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-next-button.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#snippet Fallback()} 16 | 17 | {/snippet} 18 | 19 | 29 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar-prev-button.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | {#snippet Fallback()} 16 | 17 | {/snippet} 18 | 19 | 29 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/calendar.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 20 | 28 | {#snippet children({ months, weekdays })} 29 | 30 | 31 | 32 | 33 | 34 | 35 | {#each months as month} 36 | 37 | 38 | 39 | {#each weekdays as weekday} 40 | 41 | {weekday.slice(0, 2)} 42 | 43 | {/each} 44 | 45 | 46 | 47 | {#each month.weeks as weekDates} 48 | 49 | {#each weekDates as date} 50 | 51 | 52 | 53 | {/each} 54 | 55 | {/each} 56 | 57 | 58 | {/each} 59 | 60 | {/snippet} 61 | 62 | -------------------------------------------------------------------------------- /src/lib/components/ui/calendar/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./calendar.svelte"; 2 | import Cell from "./calendar-cell.svelte"; 3 | import Day from "./calendar-day.svelte"; 4 | import Grid from "./calendar-grid.svelte"; 5 | import Header from "./calendar-header.svelte"; 6 | import Months from "./calendar-months.svelte"; 7 | import GridRow from "./calendar-grid-row.svelte"; 8 | import Heading from "./calendar-heading.svelte"; 9 | import GridBody from "./calendar-grid-body.svelte"; 10 | import GridHead from "./calendar-grid-head.svelte"; 11 | import HeadCell from "./calendar-head-cell.svelte"; 12 | import NextButton from "./calendar-next-button.svelte"; 13 | import PrevButton from "./calendar-prev-button.svelte"; 14 | 15 | export { 16 | Day, 17 | Cell, 18 | Grid, 19 | Header, 20 | Months, 21 | GridRow, 22 | Heading, 23 | GridBody, 24 | GridHead, 25 | HeadCell, 26 | NextButton, 27 | PrevButton, 28 | // 29 | Root as Calendar, 30 | }; 31 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-button.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-description.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-element-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 25 | {#snippet children({ constraints, errors, tainted, value })} 26 |
27 | {@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })} 28 |
29 | {/snippet} 30 |
31 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-field-errors.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | {#snippet children({ errors, errorProps })} 23 | {#if childrenProp} 24 | {@render childrenProp({ errors, errorProps })} 25 | {:else} 26 | {#each errors as error} 27 |
{error}
28 | {/each} 29 | {/if} 30 | {/snippet} 31 |
32 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 25 | {#snippet children({ constraints, errors, tainted, value })} 26 |
27 | {@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })} 28 |
29 | {/snippet} 30 |
31 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-fieldset.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-label.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | {#snippet child({ props })} 17 | 20 | {/snippet} 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-legend.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/index.ts: -------------------------------------------------------------------------------- 1 | import * as FormPrimitive from "formsnap"; 2 | import Description from "./form-description.svelte"; 3 | import Label from "./form-label.svelte"; 4 | import FieldErrors from "./form-field-errors.svelte"; 5 | import Field from "./form-field.svelte"; 6 | import Fieldset from "./form-fieldset.svelte"; 7 | import Legend from "./form-legend.svelte"; 8 | import ElementField from "./form-element-field.svelte"; 9 | import Button from "./form-button.svelte"; 10 | 11 | const Control = FormPrimitive.Control; 12 | 13 | export { 14 | Field, 15 | Control, 16 | Label, 17 | Button, 18 | FieldErrors, 19 | Description, 20 | Fieldset, 21 | Legend, 22 | ElementField, 23 | // 24 | Field as FormField, 25 | Control as FormControl, 26 | Description as FormDescription, 27 | Label as FormLabel, 28 | FieldErrors as FormFieldErrors, 29 | Fieldset as FormFieldset, 30 | Legend as FormLegend, 31 | ElementField as FormElementField, 32 | Button as FormButton, 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Input, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /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/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 | 16 | 17 | 18 | 28 | 29 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | import { Select as SelectPrimitive } from "bits-ui"; 2 | 3 | import GroupHeading from "./select-group-heading.svelte"; 4 | import Item from "./select-item.svelte"; 5 | import Content from "./select-content.svelte"; 6 | import Trigger from "./select-trigger.svelte"; 7 | import Separator from "./select-separator.svelte"; 8 | import ScrollDownButton from "./select-scroll-down-button.svelte"; 9 | import ScrollUpButton from "./select-scroll-up-button.svelte"; 10 | 11 | const Root = SelectPrimitive.Root; 12 | const Group = SelectPrimitive.Group; 13 | 14 | export { 15 | Root, 16 | Group, 17 | GroupHeading, 18 | Item, 19 | Content, 20 | Trigger, 21 | Separator, 22 | ScrollDownButton, 23 | ScrollUpButton, 24 | // 25 | Root as Select, 26 | Group as SelectGroup, 27 | GroupHeading as SelectGroupHeading, 28 | Item as SelectItem, 29 | Content as SelectContent, 30 | Trigger as SelectTrigger, 31 | Separator as SelectSeparator, 32 | ScrollDownButton as SelectScrollDownButton, 33 | ScrollUpButton as SelectScrollUpButton, 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-content.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 29 | 30 | 35 | {@render children?.()} 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-group-heading.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-item.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | {#snippet children({ selected, highlighted })} 26 | 27 | {#if selected} 28 | 29 | {/if} 30 | 31 | {#if childrenProp} 32 | {@render childrenProp({ selected, highlighted })} 33 | {:else} 34 | {label || value} 35 | {/if} 36 | {/snippet} 37 | 38 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-scroll-down-button.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-scroll-up-button.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-trigger.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | span]:line-clamp-1", 18 | className 19 | )} 20 | {...restProps} 21 | > 22 | {@render children?.()} 23 | 24 | 25 | -------------------------------------------------------------------------------- /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/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Snippet } from "./snippet.svelte"; -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/pre.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | 53 |
{@render props?.children?.()}
60 |
61 | -------------------------------------------------------------------------------- /src/lib/components/ui/snippet/snippet.svelte: -------------------------------------------------------------------------------- 1 | 95 | 96 | 97 | 98 |
{@html hljs.highlightAuto(snippet.content).value}
99 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from "./sonner.svelte"; 2 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /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 | 20 | -------------------------------------------------------------------------------- /src/lib/hooks/use-mouse-move.ts: -------------------------------------------------------------------------------- 1 | export function useMouseMove() { 2 | function mouseMoveEvent(e: MouseEvent) { 3 | const scale = window.visualViewport?.scale; 4 | // disable mouse movement on viewport zoom - causes page to slow down 5 | if (scale === 1) { 6 | const body = document.body; 7 | 8 | const targetX = e.clientX; 9 | const targetY = e.clientY; 10 | 11 | // the animation requires tranformX and transformY on the HTML Element 12 | body.style.setProperty("--x", `${targetX}px`); 13 | body.style.setProperty("--y", `${targetY}px`); 14 | } 15 | } 16 | 17 | document.addEventListener("mousemove", mouseMoveEvent); 18 | return () => { 19 | document.removeEventListener("mousemove", mouseMoveEvent); 20 | }; 21 | } -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /src/lib/snippets-content.ts: -------------------------------------------------------------------------------- 1 | import type { Snippet } from './types'; 2 | import { read } from '$app/server'; 3 | 4 | export async function loadAllSnippets(): Promise { 5 | const files = import.meta.glob('./snippets/**', { 6 | query: '?url', 7 | import: 'default', 8 | eager: true 9 | }); 10 | 11 | const promises = Object.entries(files).map(async ([_fileName, filePath]) => { 12 | const fileName = _fileName.replace("snippets/", "") 13 | const res = read(filePath as string); 14 | 15 | const [,folder, name] = fileName.split("/"); 16 | 17 | return { 18 | folder, 19 | slug: filePath as string, 20 | file: name, 21 | content: await res.text() 22 | }; 23 | }); 24 | 25 | return await Promise.all(promises); 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-4/time-picker-input.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 110 | 111 | { 121 | e.preventDefault(); 122 | }} 123 | {type} 124 | inputmode="decimal" 125 | onkeydown={handleKeyDown} 126 | {...$$restProps} 127 | /> 128 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-4/time-picker-utils.ts: -------------------------------------------------------------------------------- 1 | import { Time } from '@internationalized/date'; 2 | 3 | /** 4 | * regular expression to check for valid hour format (01-23) 5 | */ 6 | export function isValidHour(value: string) { 7 | return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value); 8 | } 9 | 10 | /** 11 | * regular expression to check for valid 12 hour format (01-12) 12 | */ 13 | export function isValid12Hour(value: string) { 14 | return /^(0[1-9]|1[0-2])$/.test(value); 15 | } 16 | 17 | /** 18 | * regular expression to check for valid minute format (00-59) 19 | */ 20 | export function isValidMinuteOrSecond(value: string) { 21 | return /^[0-5][0-9]$/.test(value); 22 | } 23 | 24 | type GetValidNumberConfig = { max: number; min?: number; loop?: boolean }; 25 | 26 | export function getValidNumber( 27 | value: string, 28 | { max, min = 0, loop = false }: GetValidNumberConfig 29 | ) { 30 | let numericValue = parseInt(value, 10); 31 | 32 | if (!isNaN(numericValue)) { 33 | if (!loop) { 34 | if (numericValue > max) numericValue = max; 35 | if (numericValue < min) numericValue = min; 36 | } else { 37 | if (numericValue > max) numericValue = min; 38 | if (numericValue < min) numericValue = max; 39 | } 40 | return numericValue.toString().padStart(2, "0"); 41 | } 42 | 43 | return "00"; 44 | } 45 | 46 | export function getValidHour(value: string) { 47 | if (isValidHour(value)) return value; 48 | return getValidNumber(value, { max: 23 }); 49 | } 50 | 51 | export function getValid12Hour(value: string) { 52 | if (isValid12Hour(value)) return value; 53 | return getValidNumber(value, { min: 1, max: 12 }); 54 | } 55 | 56 | export function getValidMinuteOrSecond(value: string) { 57 | if (isValidMinuteOrSecond(value)) return value; 58 | return getValidNumber(value, { max: 59 }); 59 | } 60 | 61 | type GetValidArrowNumberConfig = { 62 | min: number; 63 | max: number; 64 | step: number; 65 | }; 66 | 67 | export function getValidArrowNumber( 68 | value: string, 69 | { min, max, step }: GetValidArrowNumberConfig 70 | ) { 71 | let numericValue = parseInt(value, 10); 72 | if (!isNaN(numericValue)) { 73 | numericValue += step; 74 | return getValidNumber(String(numericValue), { min, max, loop: true }); 75 | } 76 | return "00"; 77 | } 78 | 79 | export function getValidArrowHour(value: string, step: number) { 80 | return getValidArrowNumber(value, { min: 0, max: 23, step }); 81 | } 82 | 83 | export function getValidArrow12Hour(value: string, step: number) { 84 | return getValidArrowNumber(value, { min: 1, max: 12, step }); 85 | } 86 | 87 | export function getValidArrowMinuteOrSecond(value: string, step: number) { 88 | return getValidArrowNumber(value, { min: 0, max: 59, step }); 89 | } 90 | 91 | export function setMinutes(time: Time, value: string) { 92 | const minutes = getValidMinuteOrSecond(value); 93 | return time.set({ minute: parseInt(minutes, 10) }); 94 | } 95 | 96 | export function setSeconds(time: Time, value: string) { 97 | const seconds = getValidMinuteOrSecond(value); 98 | return time.set({ second: parseInt(seconds, 10) }); 99 | } 100 | 101 | export function setHours(time: Time, value: string) { 102 | const hours = getValidHour(value); 103 | return time.set({ hour: parseInt(hours, 10) }); 104 | } 105 | 106 | export function set12Hours(time: Time, value: string, period: Period) { 107 | const hours = parseInt(getValid12Hour(value), 10); 108 | const convertedHours = convert12HourTo24Hour(hours, period); 109 | return time.set({ hour: convertedHours }); 110 | } 111 | 112 | export type TimePickerType = "minutes" | "seconds" | "hours" | "12hours"; 113 | export type Period = "AM" | "PM"; 114 | 115 | export function setDateByType( 116 | time: Time, 117 | value: string, 118 | type: TimePickerType, 119 | period?: Period 120 | ) { 121 | switch (type) { 122 | case "minutes": 123 | return setMinutes(time, value); 124 | case "seconds": 125 | return setSeconds(time, value); 126 | case "hours": 127 | return setHours(time, value); 128 | case "12hours": { 129 | if (!period) return time; 130 | return set12Hours(time, value, period); 131 | } 132 | default: 133 | return time; 134 | } 135 | } 136 | 137 | export function getDateByType(time: Time, type: TimePickerType) { 138 | switch (type) { 139 | case "minutes": 140 | return getValidMinuteOrSecond(String(time.minute)); 141 | case "seconds": 142 | return getValidMinuteOrSecond(String(time.second)); 143 | case "hours": 144 | return getValidHour(String(time.hour)); 145 | case "12hours": 146 | const hours = display12HourValue(time.hour); 147 | return getValid12Hour(String(hours)); 148 | default: 149 | return "00"; 150 | } 151 | } 152 | 153 | export function getArrowByType( 154 | value: string, 155 | step: number, 156 | type: TimePickerType 157 | ) { 158 | switch (type) { 159 | case "minutes": 160 | return getValidArrowMinuteOrSecond(value, step); 161 | case "seconds": 162 | return getValidArrowMinuteOrSecond(value, step); 163 | case "hours": 164 | return getValidArrowHour(value, step); 165 | case "12hours": 166 | return getValidArrow12Hour(value, step); 167 | default: 168 | return "00"; 169 | } 170 | } 171 | 172 | /** 173 | * handles value change of 12-hour input 174 | * 12:00 PM is 12:00 175 | * 12:00 AM is 00:00 176 | */ 177 | export function convert12HourTo24Hour(hour: number, period: Period) { 178 | if (period === "PM") { 179 | if (hour <= 11) { 180 | return hour + 12; 181 | } else { 182 | return hour; 183 | } 184 | } else if (period === "AM") { 185 | if (hour === 12) return 0; 186 | return hour; 187 | } 188 | return hour; 189 | } 190 | 191 | /** 192 | * time is stored in the 24-hour form, 193 | * but needs to be displayed to the user 194 | * in its 12-hour representation 195 | */ 196 | export function display12HourValue(hours: number) { 197 | if (hours === 0 || hours === 12) return "12"; 198 | if (hours >= 22) return `${hours - 12}`; 199 | if (hours % 12 > 9) return `${hours}`; 200 | return `0${hours % 12}`; 201 | } 202 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-4/time-picker.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
25 |
26 | {#if view === 'labels'} 27 | 28 | {/if} 29 | 30 | minuteRef?.focus()} 36 | /> 37 |
38 | 39 | {#if view === 'dotted'} 40 | : 41 | {/if} 42 | 43 |
44 | {#if view === 'labels'} 45 | 46 | {/if} 47 | 48 | hourRef?.focus()} 54 | onRightFocus={() => secondRef?.focus()} 55 | /> 56 |
57 | 58 | {#if view === 'dotted'} 59 | : 60 | {/if} 61 | 62 |
63 | {#if view === 'labels'} 64 | 65 | {/if} 66 | 67 | minuteRef?.focus()} 73 | /> 74 |
75 |
76 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/date-time-picker-form.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 49 | 50 |
51 | 52 | 53 | {#snippet children({ props })} 54 | Date of birth 55 | { 58 | if (v) { 59 | $formData.dob = v.toString(); 60 | } else { 61 | $formData.dob = ''; 62 | } 63 | }} 64 | /> 65 | Your date of birth is used to calculator your age 66 | 67 | 68 | {/snippet} 69 | 70 | 71 | 72 | {#if browser} 73 | 74 | {/if} 75 | 76 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/date-time-picker.svelte: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | 74 | 75 | {date ? df.format(date.toDate(getLocalTimeZone())) : "Pick a date"} 76 | 77 | 78 |
79 | { 82 | time && setTime(time); 83 | }} 84 | /> 85 |
86 | 87 | 88 |
89 |
90 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/time-period-select.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 72 | 73 |
74 | handleValueChange(value as Period)} 78 | > 79 | {period ?? ''} 84 | 85 | AM 86 | PM 87 | 88 | 89 |
90 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/time-picker-12h.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 |
34 |
35 | {#if view === 'labels'} 36 | 37 | {/if} 38 | 39 | minuteRef?.focus()} 46 | /> 47 |
48 | 49 | {#if view === 'dotted'} 50 | : 51 | {/if} 52 | 53 |
54 | {#if view === 'labels'} 55 | 56 | {/if} 57 | 58 | hourRef?.focus()} 64 | onRightFocus={() => secondRef?.focus()} 65 | /> 66 |
67 | 68 | {#if view === 'dotted'} 69 | : 70 | {/if} 71 | 72 |
73 | {#if view === 'labels'} 74 | 75 | {/if} 76 | 77 | minuteRef?.focus()} 83 | onRightFocus={() => periodRef?.focus()} 84 | /> 85 |
86 | 87 |
88 | {#if view === 'labels'} 89 | 90 | {/if} 91 | 92 | secondRef?.focus()} 99 | /> 100 |
101 |
102 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/time-picker-input.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 114 | 115 | { 125 | e.preventDefault(); 126 | onchange?.(e); 127 | }} 128 | {type} 129 | inputmode="decimal" 130 | onkeydown={(e) => { 131 | onkeydown?.(e); 132 | handleKeyDown(e); 133 | }} 134 | {...restProps} /> 135 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/time-picker-utils.ts: -------------------------------------------------------------------------------- 1 | import { Time } from '@internationalized/date'; 2 | 3 | /** 4 | * regular expression to check for valid hour format (01-23) 5 | */ 6 | export function isValidHour(value: string) { 7 | return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value); 8 | } 9 | 10 | /** 11 | * regular expression to check for valid 12 hour format (01-12) 12 | */ 13 | export function isValid12Hour(value: string) { 14 | return /^(0[1-9]|1[0-2])$/.test(value); 15 | } 16 | 17 | /** 18 | * regular expression to check for valid minute format (00-59) 19 | */ 20 | export function isValidMinuteOrSecond(value: string) { 21 | return /^[0-5][0-9]$/.test(value); 22 | } 23 | 24 | type GetValidNumberConfig = { max: number; min?: number; loop?: boolean }; 25 | 26 | export function getValidNumber( 27 | value: string, 28 | { max, min = 0, loop = false }: GetValidNumberConfig 29 | ) { 30 | let numericValue = parseInt(value, 10); 31 | 32 | if (!isNaN(numericValue)) { 33 | if (!loop) { 34 | if (numericValue > max) numericValue = max; 35 | if (numericValue < min) numericValue = min; 36 | } else { 37 | if (numericValue > max) numericValue = min; 38 | if (numericValue < min) numericValue = max; 39 | } 40 | return numericValue.toString().padStart(2, "0"); 41 | } 42 | 43 | return "00"; 44 | } 45 | 46 | export function getValidHour(value: string) { 47 | if (isValidHour(value)) return value; 48 | return getValidNumber(value, { max: 23 }); 49 | } 50 | 51 | export function getValid12Hour(value: string) { 52 | if (isValid12Hour(value)) return value; 53 | return getValidNumber(value, { min: 1, max: 12 }); 54 | } 55 | 56 | export function getValidMinuteOrSecond(value: string) { 57 | if (isValidMinuteOrSecond(value)) return value; 58 | return getValidNumber(value, { max: 59 }); 59 | } 60 | 61 | type GetValidArrowNumberConfig = { 62 | min: number; 63 | max: number; 64 | step: number; 65 | }; 66 | 67 | export function getValidArrowNumber( 68 | value: string, 69 | { min, max, step }: GetValidArrowNumberConfig 70 | ) { 71 | let numericValue = parseInt(value, 10); 72 | if (!isNaN(numericValue)) { 73 | numericValue += step; 74 | return getValidNumber(String(numericValue), { min, max, loop: true }); 75 | } 76 | return "00"; 77 | } 78 | 79 | export function getValidArrowHour(value: string, step: number) { 80 | return getValidArrowNumber(value, { min: 0, max: 23, step }); 81 | } 82 | 83 | export function getValidArrow12Hour(value: string, step: number) { 84 | return getValidArrowNumber(value, { min: 1, max: 12, step }); 85 | } 86 | 87 | export function getValidArrowMinuteOrSecond(value: string, step: number) { 88 | return getValidArrowNumber(value, { min: 0, max: 59, step }); 89 | } 90 | 91 | export function setMinutes(time: Time, value: string) { 92 | const minutes = getValidMinuteOrSecond(value); 93 | return time.set({ minute: parseInt(minutes, 10) }); 94 | } 95 | 96 | export function setSeconds(time: Time, value: string) { 97 | const seconds = getValidMinuteOrSecond(value); 98 | return time.set({ second: parseInt(seconds, 10) }); 99 | } 100 | 101 | export function setHours(time: Time, value: string) { 102 | const hours = getValidHour(value); 103 | return time.set({ hour: parseInt(hours, 10) }); 104 | } 105 | 106 | export function set12Hours(time: Time, value: string, period: Period) { 107 | const hours = parseInt(getValid12Hour(value), 10); 108 | const convertedHours = convert12HourTo24Hour(hours, period); 109 | return time.set({ hour: convertedHours }); 110 | } 111 | 112 | export type TimePickerType = "minutes" | "seconds" | "hours" | "12hours"; 113 | export type Period = "AM" | "PM"; 114 | 115 | export function setDateByType( 116 | time: Time, 117 | value: string, 118 | type: TimePickerType, 119 | period?: Period 120 | ) { 121 | switch (type) { 122 | case "minutes": 123 | return setMinutes(time, value); 124 | case "seconds": 125 | return setSeconds(time, value); 126 | case "hours": 127 | return setHours(time, value); 128 | case "12hours": { 129 | if (!period) return time; 130 | return set12Hours(time, value, period); 131 | } 132 | default: 133 | return time; 134 | } 135 | } 136 | 137 | export function getDateByType(time: Time, type: TimePickerType) { 138 | switch (type) { 139 | case "minutes": 140 | return getValidMinuteOrSecond(String(time.minute)); 141 | case "seconds": 142 | return getValidMinuteOrSecond(String(time.second)); 143 | case "hours": 144 | return getValidHour(String(time.hour)); 145 | case "12hours": 146 | const hours = display12HourValue(time.hour); 147 | return getValid12Hour(String(hours)); 148 | default: 149 | return "00"; 150 | } 151 | } 152 | 153 | export function getArrowByType( 154 | value: string, 155 | step: number, 156 | type: TimePickerType 157 | ) { 158 | switch (type) { 159 | case "minutes": 160 | return getValidArrowMinuteOrSecond(value, step); 161 | case "seconds": 162 | return getValidArrowMinuteOrSecond(value, step); 163 | case "hours": 164 | return getValidArrowHour(value, step); 165 | case "12hours": 166 | return getValidArrow12Hour(value, step); 167 | default: 168 | return "00"; 169 | } 170 | } 171 | 172 | /** 173 | * handles value change of 12-hour input 174 | * 12:00 PM is 12:00 175 | * 12:00 AM is 00:00 176 | */ 177 | export function convert12HourTo24Hour(hour: number, period: Period) { 178 | if (period === "PM") { 179 | if (hour <= 11) { 180 | return hour + 12; 181 | } else { 182 | return hour; 183 | } 184 | } else if (period === "AM") { 185 | if (hour === 12) return 0; 186 | return hour; 187 | } 188 | return hour; 189 | } 190 | 191 | /** 192 | * time is stored in the 24-hour form, 193 | * but needs to be displayed to the user 194 | * in its 12-hour representation 195 | */ 196 | export function display12HourValue(hours: number) { 197 | if (hours === 0 || hours === 12) return "12"; 198 | if (hours >= 22) return `${hours - 12}`; 199 | if (hours % 12 > 9) return `${hours}`; 200 | return `0${hours % 12}`; 201 | } 202 | -------------------------------------------------------------------------------- /src/lib/snippets/shadcn-svelte-runes/time-picker.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 |
28 | {#if view === 'labels'} 29 | 30 | {/if} 31 | 32 | minuteRef?.focus()} 38 | /> 39 |
40 | 41 | {#if view === 'dotted'} 42 | : 43 | {/if} 44 | 45 |
46 | {#if view === 'labels'} 47 | 48 | {/if} 49 | 50 | hourRef?.focus()} 56 | onRightFocus={() => secondRef?.focus()} 57 | /> 58 |
59 | 60 | {#if view === 'dotted'} 61 | : 62 | {/if} 63 | 64 |
65 | {#if view === 'labels'} 66 | 67 | {/if} 68 | 69 | minuteRef?.focus()} 75 | /> 76 |
77 |
78 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-4/cn.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 | } -------------------------------------------------------------------------------- /src/lib/snippets/svelte-4/time-picker-input.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 109 | 110 | { 120 | e.preventDefault(); 121 | }} 122 | {type} 123 | inputmode="decimal" 124 | onkeydown={handleKeyDown} 125 | {...$$restProps} 126 | /> 127 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-4/time-picker-utils.ts: -------------------------------------------------------------------------------- 1 | import { Time } from '@internationalized/date'; 2 | 3 | /** 4 | * regular expression to check for valid hour format (01-23) 5 | */ 6 | export function isValidHour(value: string) { 7 | return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value); 8 | } 9 | 10 | /** 11 | * regular expression to check for valid 12 hour format (01-12) 12 | */ 13 | export function isValid12Hour(value: string) { 14 | return /^(0[1-9]|1[0-2])$/.test(value); 15 | } 16 | 17 | /** 18 | * regular expression to check for valid minute format (00-59) 19 | */ 20 | export function isValidMinuteOrSecond(value: string) { 21 | return /^[0-5][0-9]$/.test(value); 22 | } 23 | 24 | type GetValidNumberConfig = { max: number; min?: number; loop?: boolean }; 25 | 26 | export function getValidNumber( 27 | value: string, 28 | { max, min = 0, loop = false }: GetValidNumberConfig 29 | ) { 30 | let numericValue = parseInt(value, 10); 31 | 32 | if (!isNaN(numericValue)) { 33 | if (!loop) { 34 | if (numericValue > max) numericValue = max; 35 | if (numericValue < min) numericValue = min; 36 | } else { 37 | if (numericValue > max) numericValue = min; 38 | if (numericValue < min) numericValue = max; 39 | } 40 | return numericValue.toString().padStart(2, "0"); 41 | } 42 | 43 | return "00"; 44 | } 45 | 46 | export function getValidHour(value: string) { 47 | if (isValidHour(value)) return value; 48 | return getValidNumber(value, { max: 23 }); 49 | } 50 | 51 | export function getValid12Hour(value: string) { 52 | if (isValid12Hour(value)) return value; 53 | return getValidNumber(value, { min: 1, max: 12 }); 54 | } 55 | 56 | export function getValidMinuteOrSecond(value: string) { 57 | if (isValidMinuteOrSecond(value)) return value; 58 | return getValidNumber(value, { max: 59 }); 59 | } 60 | 61 | type GetValidArrowNumberConfig = { 62 | min: number; 63 | max: number; 64 | step: number; 65 | }; 66 | 67 | export function getValidArrowNumber( 68 | value: string, 69 | { min, max, step }: GetValidArrowNumberConfig 70 | ) { 71 | let numericValue = parseInt(value, 10); 72 | if (!isNaN(numericValue)) { 73 | numericValue += step; 74 | return getValidNumber(String(numericValue), { min, max, loop: true }); 75 | } 76 | return "00"; 77 | } 78 | 79 | export function getValidArrowHour(value: string, step: number) { 80 | return getValidArrowNumber(value, { min: 0, max: 23, step }); 81 | } 82 | 83 | export function getValidArrow12Hour(value: string, step: number) { 84 | return getValidArrowNumber(value, { min: 1, max: 12, step }); 85 | } 86 | 87 | export function getValidArrowMinuteOrSecond(value: string, step: number) { 88 | return getValidArrowNumber(value, { min: 0, max: 59, step }); 89 | } 90 | 91 | export function setMinutes(time: Time, value: string) { 92 | const minutes = getValidMinuteOrSecond(value); 93 | return time.set({ minute: parseInt(minutes, 10) }); 94 | } 95 | 96 | export function setSeconds(time: Time, value: string) { 97 | const seconds = getValidMinuteOrSecond(value); 98 | return time.set({ second: parseInt(seconds, 10) }); 99 | } 100 | 101 | export function setHours(time: Time, value: string) { 102 | const hours = getValidHour(value); 103 | return time.set({ hour: parseInt(hours, 10) }); 104 | } 105 | 106 | export function set12Hours(time: Time, value: string, period: Period) { 107 | const hours = parseInt(getValid12Hour(value), 10); 108 | const convertedHours = convert12HourTo24Hour(hours, period); 109 | return time.set({ hour: convertedHours }); 110 | } 111 | 112 | export type TimePickerType = "minutes" | "seconds" | "hours" | "12hours"; 113 | export type Period = "AM" | "PM"; 114 | 115 | export function setDateByType( 116 | time: Time, 117 | value: string, 118 | type: TimePickerType, 119 | period?: Period 120 | ) { 121 | switch (type) { 122 | case "minutes": 123 | return setMinutes(time, value); 124 | case "seconds": 125 | return setSeconds(time, value); 126 | case "hours": 127 | return setHours(time, value); 128 | case "12hours": { 129 | if (!period) return time; 130 | return set12Hours(time, value, period); 131 | } 132 | default: 133 | return time; 134 | } 135 | } 136 | 137 | export function getDateByType(time: Time, type: TimePickerType) { 138 | switch (type) { 139 | case "minutes": 140 | return getValidMinuteOrSecond(String(time.minute)); 141 | case "seconds": 142 | return getValidMinuteOrSecond(String(time.second)); 143 | case "hours": 144 | return getValidHour(String(time.hour)); 145 | case "12hours": 146 | const hours = display12HourValue(time.hour); 147 | return getValid12Hour(String(hours)); 148 | default: 149 | return "00"; 150 | } 151 | } 152 | 153 | export function getArrowByType( 154 | value: string, 155 | step: number, 156 | type: TimePickerType 157 | ) { 158 | switch (type) { 159 | case "minutes": 160 | return getValidArrowMinuteOrSecond(value, step); 161 | case "seconds": 162 | return getValidArrowMinuteOrSecond(value, step); 163 | case "hours": 164 | return getValidArrowHour(value, step); 165 | case "12hours": 166 | return getValidArrow12Hour(value, step); 167 | default: 168 | return "00"; 169 | } 170 | } 171 | 172 | /** 173 | * handles value change of 12-hour input 174 | * 12:00 PM is 12:00 175 | * 12:00 AM is 00:00 176 | */ 177 | export function convert12HourTo24Hour(hour: number, period: Period) { 178 | if (period === "PM") { 179 | if (hour <= 11) { 180 | return hour + 12; 181 | } else { 182 | return hour; 183 | } 184 | } else if (period === "AM") { 185 | if (hour === 12) return 0; 186 | return hour; 187 | } 188 | return hour; 189 | } 190 | 191 | /** 192 | * time is stored in the 24-hour form, 193 | * but needs to be displayed to the user 194 | * in its 12-hour representation 195 | */ 196 | export function display12HourValue(hours: number) { 197 | if (hours === 0 || hours === 12) return "12"; 198 | if (hours >= 22) return `${hours - 12}`; 199 | if (hours % 12 > 9) return `${hours}`; 200 | return `0${hours % 12}`; 201 | } 202 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-4/time-picker.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |
25 | {#if view === 'labels'} 26 | 27 | {/if} 28 | 29 | minuteRef?.focus()} 35 | /> 36 |
37 | 38 | {#if view === 'dotted'} 39 | : 40 | {/if} 41 | 42 |
43 | {#if view === 'labels'} 44 | 45 | {/if} 46 | 47 | hourRef?.focus()} 53 | onRightFocus={() => secondRef?.focus()} 54 | /> 55 |
56 | 57 | {#if view === 'dotted'} 58 | : 59 | {/if} 60 | 61 |
62 | {#if view === 'labels'} 63 | 64 | {/if} 65 | 66 | minuteRef?.focus()} 72 | /> 73 |
74 |
75 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-runes/cn.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 | } -------------------------------------------------------------------------------- /src/lib/snippets/svelte-runes/time-picker-input.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 112 | 113 | { 123 | e.preventDefault(); 124 | onchange?.(e); 125 | }} 126 | {type} 127 | inputmode="decimal" 128 | onkeydown={(e) => { 129 | onkeydown?.(e); 130 | handleKeyDown(e); 131 | }} 132 | {...restProps} 133 | /> 134 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-runes/time-picker-utils.ts: -------------------------------------------------------------------------------- 1 | import { Time } from '@internationalized/date'; 2 | 3 | /** 4 | * regular expression to check for valid hour format (01-23) 5 | */ 6 | export function isValidHour(value: string) { 7 | return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value); 8 | } 9 | 10 | /** 11 | * regular expression to check for valid 12 hour format (01-12) 12 | */ 13 | export function isValid12Hour(value: string) { 14 | return /^(0[1-9]|1[0-2])$/.test(value); 15 | } 16 | 17 | /** 18 | * regular expression to check for valid minute format (00-59) 19 | */ 20 | export function isValidMinuteOrSecond(value: string) { 21 | return /^[0-5][0-9]$/.test(value); 22 | } 23 | 24 | type GetValidNumberConfig = { max: number; min?: number; loop?: boolean }; 25 | 26 | export function getValidNumber( 27 | value: string, 28 | { max, min = 0, loop = false }: GetValidNumberConfig 29 | ) { 30 | let numericValue = parseInt(value, 10); 31 | 32 | if (!isNaN(numericValue)) { 33 | if (!loop) { 34 | if (numericValue > max) numericValue = max; 35 | if (numericValue < min) numericValue = min; 36 | } else { 37 | if (numericValue > max) numericValue = min; 38 | if (numericValue < min) numericValue = max; 39 | } 40 | return numericValue.toString().padStart(2, "0"); 41 | } 42 | 43 | return "00"; 44 | } 45 | 46 | export function getValidHour(value: string) { 47 | if (isValidHour(value)) return value; 48 | return getValidNumber(value, { max: 23 }); 49 | } 50 | 51 | export function getValid12Hour(value: string) { 52 | if (isValid12Hour(value)) return value; 53 | return getValidNumber(value, { min: 1, max: 12 }); 54 | } 55 | 56 | export function getValidMinuteOrSecond(value: string) { 57 | if (isValidMinuteOrSecond(value)) return value; 58 | return getValidNumber(value, { max: 59 }); 59 | } 60 | 61 | type GetValidArrowNumberConfig = { 62 | min: number; 63 | max: number; 64 | step: number; 65 | }; 66 | 67 | export function getValidArrowNumber( 68 | value: string, 69 | { min, max, step }: GetValidArrowNumberConfig 70 | ) { 71 | let numericValue = parseInt(value, 10); 72 | if (!isNaN(numericValue)) { 73 | numericValue += step; 74 | return getValidNumber(String(numericValue), { min, max, loop: true }); 75 | } 76 | return "00"; 77 | } 78 | 79 | export function getValidArrowHour(value: string, step: number) { 80 | return getValidArrowNumber(value, { min: 0, max: 23, step }); 81 | } 82 | 83 | export function getValidArrow12Hour(value: string, step: number) { 84 | return getValidArrowNumber(value, { min: 1, max: 12, step }); 85 | } 86 | 87 | export function getValidArrowMinuteOrSecond(value: string, step: number) { 88 | return getValidArrowNumber(value, { min: 0, max: 59, step }); 89 | } 90 | 91 | export function setMinutes(time: Time, value: string) { 92 | const minutes = getValidMinuteOrSecond(value); 93 | return time.set({ minute: parseInt(minutes, 10) }); 94 | } 95 | 96 | export function setSeconds(time: Time, value: string) { 97 | const seconds = getValidMinuteOrSecond(value); 98 | return time.set({ second: parseInt(seconds, 10) }); 99 | } 100 | 101 | export function setHours(time: Time, value: string) { 102 | const hours = getValidHour(value); 103 | return time.set({ hour: parseInt(hours, 10) }); 104 | } 105 | 106 | export function set12Hours(time: Time, value: string, period: Period) { 107 | const hours = parseInt(getValid12Hour(value), 10); 108 | const convertedHours = convert12HourTo24Hour(hours, period); 109 | return time.set({ hour: convertedHours }); 110 | } 111 | 112 | export type TimePickerType = "minutes" | "seconds" | "hours" | "12hours"; 113 | export type Period = "AM" | "PM"; 114 | 115 | export function setDateByType( 116 | time: Time, 117 | value: string, 118 | type: TimePickerType, 119 | period?: Period 120 | ) { 121 | switch (type) { 122 | case "minutes": 123 | return setMinutes(time, value); 124 | case "seconds": 125 | return setSeconds(time, value); 126 | case "hours": 127 | return setHours(time, value); 128 | case "12hours": { 129 | if (!period) return time; 130 | return set12Hours(time, value, period); 131 | } 132 | default: 133 | return time; 134 | } 135 | } 136 | 137 | export function getDateByType(time: Time, type: TimePickerType) { 138 | switch (type) { 139 | case "minutes": 140 | return getValidMinuteOrSecond(String(time.minute)); 141 | case "seconds": 142 | return getValidMinuteOrSecond(String(time.second)); 143 | case "hours": 144 | return getValidHour(String(time.hour)); 145 | case "12hours": 146 | const hours = display12HourValue(time.hour); 147 | return getValid12Hour(String(hours)); 148 | default: 149 | return "00"; 150 | } 151 | } 152 | 153 | export function getArrowByType( 154 | value: string, 155 | step: number, 156 | type: TimePickerType 157 | ) { 158 | switch (type) { 159 | case "minutes": 160 | return getValidArrowMinuteOrSecond(value, step); 161 | case "seconds": 162 | return getValidArrowMinuteOrSecond(value, step); 163 | case "hours": 164 | return getValidArrowHour(value, step); 165 | case "12hours": 166 | return getValidArrow12Hour(value, step); 167 | default: 168 | return "00"; 169 | } 170 | } 171 | 172 | /** 173 | * handles value change of 12-hour input 174 | * 12:00 PM is 12:00 175 | * 12:00 AM is 00:00 176 | */ 177 | export function convert12HourTo24Hour(hour: number, period: Period) { 178 | if (period === "PM") { 179 | if (hour <= 11) { 180 | return hour + 12; 181 | } else { 182 | return hour; 183 | } 184 | } else if (period === "AM") { 185 | if (hour === 12) return 0; 186 | return hour; 187 | } 188 | return hour; 189 | } 190 | 191 | /** 192 | * time is stored in the 24-hour form, 193 | * but needs to be displayed to the user 194 | * in its 12-hour representation 195 | */ 196 | export function display12HourValue(hours: number) { 197 | if (hours === 0 || hours === 12) return "12"; 198 | if (hours >= 22) return `${hours - 12}`; 199 | if (hours % 12 > 9) return `${hours}`; 200 | return `0${hours % 12}`; 201 | } 202 | -------------------------------------------------------------------------------- /src/lib/snippets/svelte-runes/time-picker.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |
25 | {#if view === 'labels'} 26 | 27 | {/if} 28 | 29 | minuteRef?.focus()} 35 | /> 36 |
37 | 38 | {#if view === 'dotted'} 39 | : 40 | {/if} 41 | 42 |
43 | {#if view === 'labels'} 44 | 45 | {/if} 46 | 47 | hourRef?.focus()} 53 | onRightFocus={() => secondRef?.focus()} 54 | /> 55 |
56 | 57 | {#if view === 'dotted'} 58 | : 59 | {/if} 60 | 61 |
62 | {#if view === 'labels'} 63 | 64 | {/if} 65 | 66 | minuteRef?.focus()} 72 | /> 73 |
74 |
75 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | export type Snippet = { 2 | folder: string; 3 | slug: string; 4 | file: string; 5 | content: string; 6 | } -------------------------------------------------------------------------------- /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 | 10 | 11 | 12 | 13 | 41 | 42 | 43 | 44 | 45 | {@render children()} 46 | 47 | -------------------------------------------------------------------------------- /src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { loadAllSnippets } from '$lib/snippets-content'; 2 | 3 | export async function load() { 4 | return { 5 | snippets: await loadAllSnippets() 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 |
50 |
51 |
54 | 55 | 56 | 64 |
65 |
66 |
69 |
70 |

Time Picker

71 |

72 | A {``} component built with Svelte and Shadcn UI or Headless. 73 |

74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 |

Demo

83 | 84 |
85 |
86 |
    87 |
  • 88 | Listens to keydown events 89 |
  • 90 |
  • Supports arrow navigation
  • 91 |
  • Formats date values
  • 92 |
  • Optimizes mobile keyboard
  • 93 |
94 |
95 |
96 |
99 |

Get Started

100 | 160 |
161 |
162 |
163 |

Snippets

164 | 165 | 166 | 167 | {#each snippetKeys as snippetKey} 168 | {snippetKey} 169 | {/each} 170 | 171 | 172 | 173 | {#if snippets[currentKey]} 174 | 175 | {#each snippets[currentKey] as snippet (snippet.slug)} 176 | 177 | 178 | {snippet.file} 179 | 180 | 181 | 182 | 183 | 184 | 185 | {/each} 186 | 187 | {/if} 188 |
189 |
190 |
191 |

192 | Originally created by 196 | OpenStatus 197 | 198 | 199 |
200 | 201 | adapted to Svelte by 202 | 203 | 1bye 204 | 205 |

206 |
207 |
208 |
209 | -------------------------------------------------------------------------------- /src/routes/_background.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |
18 |
19 |
22 | 35 |
36 |
37 | 38 | {@render children?.()} 39 | -------------------------------------------------------------------------------- /src/routes/_og-image.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
13 |
14 |

Time Picker

15 |

16 | A {``}{' '} 17 | component built with Svelte and Shadcn UI. 18 |

19 | 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1bye/time-picker-svelte/7016b82b8f42bdc48cdf10ff1164ee31d5024e84/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://svelte.dev/docs/kit/integrations 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://svelte.dev/docs/kit/adapters for more information about adapters. 14 | adapter: adapter({ 15 | runtime: 'nodejs20.x' 16 | }), 17 | alias: { 18 | '$lib/*': './src/lib/*' 19 | } 20 | } 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { fontFamily } from "tailwindcss/defaultTheme"; 2 | import type { Config } from "tailwindcss"; 3 | import tailwindcssAnimate from "tailwindcss-animate"; 4 | 5 | const config: Config = { 6 | darkMode: ["class"], 7 | content: ["./src/**/*.{html,js,svelte,ts}"], 8 | safelist: ["dark"], 9 | theme: { 10 | backgroundImage: { 11 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 12 | }, 13 | container: { 14 | center: true, 15 | padding: "2rem", 16 | screens: { 17 | "2xl": "1400px" 18 | } 19 | }, 20 | extend: { 21 | colors: { 22 | border: "hsl(var(--border) / )", 23 | input: "hsl(var(--input) / )", 24 | ring: "hsl(var(--ring) / )", 25 | background: "hsl(var(--background) / )", 26 | foreground: "hsl(var(--foreground) / )", 27 | primary: { 28 | DEFAULT: "hsl(var(--primary) / )", 29 | foreground: "hsl(var(--primary-foreground) / )" 30 | }, 31 | secondary: { 32 | DEFAULT: "hsl(var(--secondary) / )", 33 | foreground: "hsl(var(--secondary-foreground) / )" 34 | }, 35 | destructive: { 36 | DEFAULT: "hsl(var(--destructive) / )", 37 | foreground: "hsl(var(--destructive-foreground) / )" 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted) / )", 41 | foreground: "hsl(var(--muted-foreground) / )" 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent) / )", 45 | foreground: "hsl(var(--accent-foreground) / )" 46 | }, 47 | popover: { 48 | DEFAULT: "hsl(var(--popover) / )", 49 | foreground: "hsl(var(--popover-foreground) / )" 50 | }, 51 | card: { 52 | DEFAULT: "hsl(var(--card) / )", 53 | foreground: "hsl(var(--card-foreground) / )" 54 | }, 55 | sidebar: { 56 | DEFAULT: "hsl(var(--sidebar-background))", 57 | foreground: "hsl(var(--sidebar-foreground))", 58 | primary: "hsl(var(--sidebar-primary))", 59 | "primary-foreground": "hsl(var(--sidebar-primary-foreground))", 60 | accent: "hsl(var(--sidebar-accent))", 61 | "accent-foreground": "hsl(var(--sidebar-accent-foreground))", 62 | border: "hsl(var(--sidebar-border))", 63 | ring: "hsl(var(--sidebar-ring))", 64 | }, 65 | }, 66 | borderRadius: { 67 | xl: "calc(var(--radius) + 4px)", 68 | lg: "var(--radius)", 69 | md: "calc(var(--radius) - 2px)", 70 | sm: "calc(var(--radius) - 4px)" 71 | }, 72 | fontFamily: { 73 | sans: ["Open Sans", ...fontFamily.sans], 74 | cal: ["Open Sans"], 75 | }, 76 | keyframes: { 77 | "accordion-down": { 78 | from: { height: "0" }, 79 | to: { height: "var(--bits-accordion-content-height)" }, 80 | }, 81 | "accordion-up": { 82 | from: { height: "var(--bits-accordion-content-height)" }, 83 | to: { height: "0" }, 84 | }, 85 | "caret-blink": { 86 | "0%,70%,100%": { opacity: "1" }, 87 | "20%,50%": { opacity: "0" }, 88 | }, 89 | }, 90 | animation: { 91 | "accordion-down": "accordion-down 0.2s ease-out", 92 | "accordion-up": "accordion-up 0.2s ease-out", 93 | "caret-blink": "caret-blink 1.25s ease-out infinite", 94 | }, 95 | }, 96 | }, 97 | plugins: [tailwindcssAnimate], 98 | }; 99 | 100 | export default config; 101 | -------------------------------------------------------------------------------- /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://svelte.dev/docs/kit/configuration#alias 15 | // except $lib which is handled by https://svelte.dev/docs/kit/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 | --------------------------------------------------------------------------------