[READ-ONLY] Mirror of https://github.com/flo-bit/ui-kit. 🦊 fox ui, svelte 5 and tailwind 4 flo-bit.dev/ui-kit/
svelte tailwindcss ui-components
0

Configure Feed

Select the types of activity you want to include in your feed.

some cleanup, add color picker start

Florian (Mar 8, 2025, 1:13 AM +0100) 6ee165cb ac54005c

+1348 -456
+1 -1
README.md
··· 68 68 Some components might need additional dependencies, just install once you get an error (recommended), or install all of them at once: 69 69 70 70 ```bash 71 - npm install bits-ui tailwind-variants @tailwindcss/forms @tailwindcss/typography layerchart plyr svelte-sonner tailwindcss-animate three @threlte/extras @threlte/core @types/three @zerodevx/svelte-img @number-flow/svelte wavesurfer.js hls.js 71 + npm install bits-ui tailwind-variants @tailwindcss/forms @tailwindcss/typography layerchart plyr svelte-sonner tailwindcss-animate three @threlte/extras @threlte/core @types/three @zerodevx/svelte-img @number-flow/svelte wavesurfer.js hls.js @texel/color 72 72 ``` 73 73 74 74 again tailwind plugins should be added to your `app.css`
+25 -4
package-lock.json
··· 11 11 "@atproto/api": "^0.14.7", 12 12 "@number-flow/svelte": "^0.3.3", 13 13 "@sveltejs/adapter-static": "^3.0.8", 14 + "@texel/color": "^1.1.9", 14 15 "@thisux/sveltednd": "^0.0.20", 15 16 "@threlte/core": "^8.0.1", 16 17 "@threlte/extras": "^9.0.1", ··· 25 26 "tailwind-variants": "^0.3.1", 26 27 "tailwindcss-animate": "^1.0.7", 27 28 "three": "^0.173.0", 29 + "vite-plugin-glsl": "^1.3.1", 28 30 "wavesurfer.js": "^7.9.1" 29 31 }, 30 32 "devDependencies": { ··· 1458 1460 "version": "5.1.4", 1459 1461 "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", 1460 1462 "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", 1461 - "dev": true, 1462 1463 "dependencies": { 1463 1464 "@types/estree": "^1.0.0", 1464 1465 "estree-walker": "^2.0.2", ··· 2046 2047 "peerDependencies": { 2047 2048 "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" 2048 2049 } 2050 + }, 2051 + "node_modules/@texel/color": { 2052 + "version": "1.1.9", 2053 + "resolved": "https://registry.npmjs.org/@texel/color/-/color-1.1.9.tgz", 2054 + "integrity": "sha512-dXGidZoaaHHdc/8L8PBHAEl3F74s/+rOufcL3yYcBjJgv4btRmPr28owjGYmijs5GRAJ10E9rEIoHnrB1eEAGA==", 2055 + "license": "MIT" 2049 2056 }, 2050 2057 "node_modules/@thisux/sveltednd": { 2051 2058 "version": "0.0.20", ··· 3428 3435 "node_modules/estree-walker": { 3429 3436 "version": "2.0.2", 3430 3437 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 3431 - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 3432 - "dev": true 3438 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 3433 3439 }, 3434 3440 "node_modules/esutils": { 3435 3441 "version": "2.0.3", ··· 4653 4659 "version": "4.0.2", 4654 4660 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 4655 4661 "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 4656 - "dev": true, 4657 4662 "engines": { 4658 4663 "node": ">=12" 4659 4664 }, ··· 6118 6123 }, 6119 6124 "engines": { 6120 6125 "node": ">=12.0.0" 6126 + } 6127 + }, 6128 + "node_modules/vite-plugin-glsl": { 6129 + "version": "1.3.1", 6130 + "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.1.tgz", 6131 + "integrity": "sha512-iClII8Idb9X0m6nS0YI2cWWXbBuT5EKKw5kXSAuRu4RJsNe4oypxKXE7jx0XMoyqij2s8WL0ZLfou801mpkREg==", 6132 + "license": "MIT", 6133 + "dependencies": { 6134 + "@rollup/pluginutils": "^5.1.0" 6135 + }, 6136 + "engines": { 6137 + "node": ">= 16.15.1", 6138 + "npm": ">= 8.11.0" 6139 + }, 6140 + "peerDependencies": { 6141 + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" 6121 6142 } 6122 6143 }, 6123 6144 "node_modules/vitefu": {
+2
package.json
··· 42 42 "@atproto/api": "^0.14.7", 43 43 "@number-flow/svelte": "^0.3.3", 44 44 "@sveltejs/adapter-static": "^3.0.8", 45 + "@texel/color": "^1.1.9", 45 46 "@thisux/sveltednd": "^0.0.20", 46 47 "@threlte/core": "^8.0.1", 47 48 "@threlte/extras": "^9.0.1", ··· 56 57 "tailwind-variants": "^0.3.1", 57 58 "tailwindcss-animate": "^1.0.7", 58 59 "three": "^0.173.0", 60 + "vite-plugin-glsl": "^1.3.1", 59 61 "wavesurfer.js": "^7.9.1" 60 62 } 61 63 }
+2 -1
vite.config.ts
··· 1 1 import { sveltekit } from '@sveltejs/kit/vite'; 2 2 import { defineConfig } from 'vite'; 3 3 import { imagetools } from '@zerodevx/svelte-img/vite'; 4 + import glsl from 'vite-plugin-glsl'; 4 5 5 6 export default defineConfig({ 6 - plugins: [sveltekit(), imagetools()] 7 + plugins: [sveltekit(), imagetools(), glsl()] 7 8 });
+13 -275
src/routes/+page.svelte
··· 1 1 <script> 2 2 import { base } from '$app/paths'; 3 - import CardAlerts from '$lib/cards/CardAlerts.svelte'; 4 - import CardAvatar from '$lib/cards/CardAvatar.svelte'; 5 - import CardBadge from '$lib/cards/CardBadge.svelte'; 6 - import CardBox from '$lib/cards/CardBox.svelte'; 7 - import CardButtons from '$lib/cards/CardButtons.svelte'; 8 - import CardCard from '$lib/cards/CardCard.svelte'; 9 - import CardChatBubble from '$lib/cards/CardChatBubble.svelte'; 10 - import CardCheckbox from '$lib/cards/CardCheckbox.svelte'; 11 - import CardGithubCorner from '$lib/cards/CardGithubCorner.svelte'; 12 - import CardHead from '$lib/cards/CardHead.svelte'; 13 - import CardImage from '$lib/cards/CardImage.svelte'; 14 - import CardInput from '$lib/cards/CardInput.svelte'; 15 - import CardModal from '$lib/cards/CardModal.svelte'; 16 - import CardNumberInput from '$lib/cards/CardNumberInput.svelte'; 17 - import CardScrollArea from '$lib/cards/CardScrollArea.svelte'; 18 - import CardSlider from '$lib/cards/CardSlider.svelte'; 19 - import CardSonner from '$lib/cards/CardSonner.svelte'; 20 - import CardSwitch from '$lib/cards/CardSwitch.svelte'; 21 - import CardTextarea from '$lib/cards/CardTextarea.svelte'; 22 - import CardTooltip from '$lib/cards/CardTooltip.svelte'; 23 - import CardHeatmap from '$lib/cards/graphs/CardHeatmap.svelte'; 24 - import CardLineGraph from '$lib/cards/graphs/CardLineGraph.svelte'; 25 - import CardRingChart from '$lib/cards/graphs/CardRingChart.svelte'; 26 - import Badge from '$lib/components/base/badge/Badge.svelte'; 27 - import { buttonVariants } from '$lib/components/base/button'; 28 - import Navbar from '$lib/components/base/navbar/Navbar.svelte'; 29 - import * as Popover from '$lib/components/base/popover'; 30 - 31 - import { ThemeToggle } from '$lib/components/base/theme-toggle'; 32 - import { Github } from '$lib/components/extra/social-icons'; 33 - import Logo from '$lib/docs/Logo.svelte'; 34 - import SelectTheme from '$lib/preview/SelectTheme.svelte'; 35 - import { cn } from '$lib/utils'; 36 - 37 - let cards = [ 38 - { 39 - component: CardAlerts, 40 - className: '', 41 - label: 'Alerts', 42 - href: '/alert' 43 - }, 44 - { 45 - component: CardAvatar, 46 - className: '', 47 - label: 'Avatars', 48 - href: '/avatar' 49 - }, 50 - { 51 - component: CardBadge, 52 - className: '', 53 - label: 'Badges', 54 - href: '/badge' 55 - }, 56 - { 57 - component: CardBox, 58 - className: '', 59 - label: 'Boxes', 60 - href: '/box' 61 - }, 62 - { 63 - component: CardButtons, 64 - className: '', 65 - label: 'Buttons', 66 - href: '/button' 67 - }, 68 - { 69 - component: CardChatBubble, 70 - className: '', 71 - label: 'Chat Bubbles', 72 - href: '/chat-bubble' 73 - }, 74 - { 75 - component: CardCheckbox, 76 - className: '', 77 - label: 'Checkboxes', 78 - href: '/checkbox' 79 - }, 80 - { 81 - component: CardImage, 82 - className: '', 83 - label: 'Images', 84 - href: '/image' 85 - }, 86 - { 87 - component: CardInput, 88 - className: '', 89 - label: 'Inputs', 90 - href: '/input' 91 - }, 92 - { 93 - component: CardModal, 94 - className: '', 95 - label: 'Modals', 96 - href: '/modal' 97 - }, 98 - { 99 - component: CardNumberInput, 100 - className: '', 101 - label: 'Number Inputs', 102 - href: '/number-input' 103 - }, 104 - { 105 - component: CardScrollArea, 106 - className: 'p-2', 107 - label: 'Scroll Areas', 108 - href: '/scroll-area' 109 - }, 110 - { 111 - component: CardSlider, 112 - className: '', 113 - label: 'Sliders', 114 - href: '/slider' 115 - }, 116 - { 117 - component: CardSonner, 118 - className: '', 119 - label: 'Sonner', 120 - href: '/sonner' 121 - }, 122 - { 123 - component: CardSwitch, 124 - className: '', 125 - label: 'Switches', 126 - href: '/switch' 127 - }, 128 - { 129 - component: CardTextarea, 130 - className: '', 131 - label: 'Textareas', 132 - href: '/textarea' 133 - }, 134 - { 135 - component: CardTooltip, 136 - className: '', 137 - label: 'Tooltips', 138 - href: '/tooltip' 139 - }, 140 - { 141 - component: CardCard, 142 - className: '', 143 - label: 'Cards', 144 - href: '/image-card' 145 - }, 146 - { 147 - component: CardGithubCorner, 148 - className: '', 149 - label: 'Github Corner', 150 - href: '/github-corner' 151 - }, 152 - { 153 - component: CardHead, 154 - className: 'px-0 gap-0', 155 - label: 'Head', 156 - href: '/head' 157 - } 158 - ]; 159 - 160 - // sort cards by label 161 - cards.sort((a, b) => a.label.localeCompare(b.label)); 162 - 163 - let graphCards = [ 164 - { 165 - component: CardHeatmap, 166 - className: '', 167 - label: 'Heatmap', 168 - href: '/heatmap' 169 - }, 170 - { 171 - component: CardLineGraph, 172 - className: '', 173 - label: 'Line Graph', 174 - href: '/line-graph' 175 - }, 176 - { 177 - component: CardRingChart, 178 - className: '', 179 - label: 'Ring Chart', 180 - href: '/ring-chart' 181 - } 182 - ]; 183 - 184 - // sort graphCards by label 185 - graphCards.sort((a, b) => a.label.localeCompare(b.label)); 3 + import { Button } from '$lib/components/base/button'; 4 + import Cards from '$lib/docs/Cards.svelte'; 5 + import Navbar from '$lib/docs/Navbar.svelte'; 6 + import Sidebar from '$lib/docs/Sidebar.svelte'; 186 7 </script> 187 8 188 - <Navbar> 189 - <div class="flex items-center gap-2 ml-3"> 190 - <Logo /> 191 - </div> 192 - <div class="flex items-end gap-4"> 193 - <Popover.Root> 194 - <Popover.Trigger 195 - class={cn( 196 - buttonVariants({ variant: 'link', size: 'default' }), 197 - 'flex cursor-pointer items-center gap-0 -space-x-2' 198 - )} 199 - > 200 - <div 201 - class=" from-accent-500 to-accent-600 border-accent-700 dark:border-accent-400 z-10 size-6 rounded-full border bg-gradient-to-b" 202 - ></div> 9 + <Navbar /> 10 + <Sidebar mobileOnly /> 203 11 204 - <div 205 - class=" from-base-500 to-base-600 border-base-700 dark:border-base-400 size-6 rounded-full border bg-gradient-to-b" 206 - ></div> 207 - </Popover.Trigger> 208 - <Popover.Content> 209 - <SelectTheme showTitle={false} /> 210 - </Popover.Content> 211 - </Popover.Root> 212 - <ThemeToggle /> 213 - 214 - <Github href="https://github.com/flo-bit/ui-kit" class="py-2 pl-2 pr-4" /> 215 - </div> 216 - </Navbar> 217 - 218 - <div> 219 - <div 220 - class="mx-auto flex w-full max-w-7xl flex-col items-start justify-center gap-2 px-8 py-24" 12 + <div class="mx-auto flex w-full max-w-7xl flex-col items-start justify-center gap-2 px-8 py-24"> 13 + <h1 14 + class="text-base-950 dark:text-base-50 my-8 mt-8 max-w-2xl text-4xl font-bold tracking-tight text-pretty md:text-5xl md:leading-14" 221 15 > 222 - <h1 class="text-4xl md:leading-14 text-pretty md:text-5xl font-bold mt-8 my-12 text-base-950 dark:text-base-50 max-w-2xl tracking-tight"> 223 - Easy to use UI components built with Tailwind and Svelte 224 - </h1> 225 - <h2 class="text-xl font-medium mb-4 text-base-800 dark:text-base-200">Base components</h2> 226 - <div class="grid w-full grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"> 227 - {#each cards as card} 228 - <div class="group relative transition-opacity duration-150 hover:opacity-90 flex flex-col gap-3 md:gap-4 items-start"> 229 - <div 230 - class="bg-base-100 pointer-events-none w-full dark:bg-base-900/30 border-base-200 dark:border-base-900 relative h-44 rounded-2xl border overflow-hidden" 231 - > 232 - <div 233 - class={cn( 234 - 'absolute will-change-transform inset-0 flex h-full w-full flex-col items-center justify-center gap-2 p-6 transition-transform duration-300 group-hover:scale-103', 235 - card.className, 236 - )} 237 - aria-hidden="true" 238 - tabindex="-1" 239 - > 240 - <card.component /> 241 - </div> 242 - </div> 243 - <a 244 - href={base + `/components${card.href}`} 245 - class="focus-visible:outline-2 rounded-xl focus-visible:outline-accent-500 focus-visible:outline-offset-2" 246 - > 247 - <span class="mx-2 text-sm font-semibold text-base-900 dark:text-base-50">{card.label}</span> 248 - <span class="absolute inset-0"></span> 249 - </a> 250 - </div> 251 - {/each} 252 - </div> 16 + Easy to use UI components built with Tailwind and Svelte 17 + </h1> 253 18 19 + <Button href={base + "/components"} class="mb-8">All Components <span>&rarr;</span></Button> 254 20 255 - <h2 class="text-xl font-medium mb-4 text-base-800 dark:text-base-200 mt-24">Graph components</h2> 256 - <div class="grid w-full grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"> 257 - {#each graphCards as card} 258 - <div class="group relative transition-opacity duration-150 hover:opacity-90 flex flex-col gap-3 md:gap-4 items-start"> 259 - <div 260 - class="bg-base-100 pointer-events-none w-full dark:bg-base-900/30 border-base-200 dark:border-base-900 relative h-44 rounded-2xl border overflow-hidden" 261 - > 262 - <div 263 - class={cn( 264 - 'absolute will-change-transform inset-0 flex h-full w-full flex-col items-center justify-center gap-2 p-6 transition-transform duration-300 group-hover:scale-103', 265 - card.className, 266 - )} 267 - aria-hidden="true" 268 - tabindex="-1" 269 - > 270 - <card.component /> 271 - </div> 272 - </div> 273 - <a 274 - href={base + `/components${card.href}`} 275 - class="focus-visible:outline-2 rounded-xl focus-visible:outline-accent-500 focus-visible:outline-offset-2" 276 - > 277 - <span class="mx-2 text-sm font-semibold text-base-900 dark:text-base-50">{card.label}</span> 278 - <span class="absolute inset-0"></span> 279 - </a> 280 - </div> 281 - {/each} 282 - </div> 283 - </div> 21 + <Cards /> 284 22 </div>
+2
src/lib/assets/demo.svg
··· 1 + <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1098.1581271772438 257.514568002161" width="1098.1581271772438" height="257.514568002161" filter="invert(93%) hue-rotate(180deg)"><!-- svg-source:excalidraw --><metadata></metadata><defs><style class="style-fonts"> 2 + @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }</style></defs><g stroke-linecap="round" transform="translate(10 66.78471109388693) rotate(0 135.6523653909527 70.56376046809953)"><path d="M2.36 1.01 C62.02 0.61, 129.56 0.81, 271.55 0.7 M-0.28 -0.3 C74.51 2.34, 152.38 2.46, 271.81 -1.51 M269.82 -3.16 C268 53.81, 267.38 100.6, 271.78 137.65 M272.28 -1.06 C272.55 33.25, 272.49 64.42, 273.3 142.13 M270.6 139.4 C182.56 137.36, 91.46 139.85, 1.81 142.58 M272.4 140.28 C208.45 140.81, 148.3 140.54, 1.37 140.02 M2.02 138.57 C4.78 104.07, 3.76 69.39, -3.98 3.69 M0.05 141.42 C0.16 96.56, -0.37 48.86, -0.53 0.42" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(15.926413242515082 92.34847156198657) rotate(0 129.7259521484375 45)"><text x="129.7259521484375" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Create a graph</text><text x="129.7259521484375" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">in Excalidraw</text></g><g stroke-linecap="round" transform="translate(815.8202439362449 39.11611475625432) rotate(0 136.16894162049948 102.5)"><path d="M141.45 -1.23 C155.91 -2.36, 170.51 3.94, 184.58 8.62 C198.66 13.31, 214.23 19.12, 225.91 26.88 C237.59 34.64, 246.89 44.84, 254.67 55.18 C262.45 65.51, 270.06 77.6, 272.58 88.89 C275.1 100.18, 273.43 111.79, 269.78 122.93 C266.12 134.07, 258.71 145.68, 250.63 155.71 C242.55 165.74, 233.23 175.99, 221.28 183.09 C209.32 190.18, 193.98 194.51, 178.93 198.28 C163.88 202.05, 146.8 205.97, 130.97 205.69 C115.14 205.41, 98.04 201.1, 83.94 196.59 C69.84 192.09, 57.41 186.21, 46.38 178.65 C35.35 171.09, 24.91 161.24, 17.74 151.23 C10.58 141.22, 5.72 130.31, 3.37 118.58 C1.02 106.86, 0.65 92.79, 3.62 80.89 C6.59 68.99, 13.35 56.95, 21.22 47.18 C29.09 37.4, 38.55 29.5, 50.83 22.24 C63.12 14.99, 71.01 5.7, 94.91 3.64 C118.82 1.58, 171.2 4.67, 194.26 9.89 C217.32 15.1, 234.22 33.27, 233.26 34.95 M203.6 12.5 C218.33 17.03, 232.15 27.6, 241.88 36.35 C251.61 45.1, 257.25 53.87, 261.99 65.01 C266.74 76.15, 270.04 91.42, 270.37 103.2 C270.7 114.99, 269.27 125.2, 263.98 135.72 C258.69 146.24, 248.3 156.67, 238.63 166.3 C228.96 175.93, 219.18 187.64, 205.96 193.49 C192.74 199.35, 174.58 200.09, 159.32 201.42 C144.05 202.76, 129.48 202.91, 114.35 201.49 C99.22 200.08, 81.65 198.49, 68.54 192.92 C55.42 187.34, 45.2 177.1, 35.66 168.04 C26.12 158.97, 16.99 149.14, 11.29 138.53 C5.58 127.92, 2.25 116.17, 1.43 104.4 C0.6 92.62, 1.31 78.9, 6.32 67.88 C11.32 56.86, 21.53 46.96, 31.47 38.28 C41.4 29.6, 52.53 21.77, 65.9 15.8 C79.28 9.82, 96.58 4.4, 111.7 2.43 C126.82 0.46, 141.25 2.51, 156.62 3.97 C172 5.42, 196.48 8.95, 203.95 11.17 C211.42 13.38, 202.23 15.24, 201.45 17.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(861.3752311377848 96.63766968463324) rotate(0 90.82797241210938 45)"><text x="90.82797241210938" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Use as a</text><text x="90.82797241210938" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">component</text></g><g stroke-linecap="round" transform="translate(414.3162037907905 10) rotate(0 144.20211635590613 118.7572840010805)"><path d="M144.57 -1.12 C174.79 28.1, 207.41 49.63, 289.34 117.32 M145.56 -0.38 C180.67 26.31, 214.82 57.02, 287.26 119.16 M289.43 122.78 C259.71 139.8, 232.66 168.59, 148.87 235.79 M288.18 117.29 C251.8 146.89, 218.34 175.86, 143.52 239.13 M147.41 233.95 C111.22 210.22, 79.78 182.55, -2.18 118.24 M143.29 237.23 C99.77 203.03, 57.43 169.51, 1.94 118.46 M-1.39 117.43 C37.07 92.31, 79.74 55.81, 145 0.92 M-1.47 117.83 C47.61 77.49, 99.41 38.32, 143.32 0.17" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(495.5432843076105 83.87864200054037) rotate(0 62.87397766113281 45)"><text x="62.87397766113281" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Export</text><text x="62.87397766113281" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">as SVG</text></g><g stroke-linecap="round"><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M-1.57 1.67 C17.35 1.51, 94.72 -0.86, 114.68 -1.33 M2.78 0.11 C22.41 0.44, 100.62 1.89, 119.64 2.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M97.2 8.44 C103.97 8.32, 112.2 5.39, 120.1 1.21 M96.34 10.89 C100.25 8.35, 106.12 6.14, 119.97 3.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M97.5 -8.66 C104.15 -2.92, 112.27 0.01, 120.1 1.21 M96.64 -6.21 C100.34 -5.04, 106.14 -3.53, 119.97 3.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M-0.2 1.49 C17.61 1.93, 88.56 2.77, 106.41 3.26 M-3.76 -0.17 C13.94 0.49, 87.03 4.4, 105.42 5.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M82.54 14.41 C86.51 12.75, 94.14 10.16, 105.52 7.23 M81.56 12.41 C87.56 10.69, 91.65 9.98, 106.21 5.41" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M83.53 -2.66 C87.26 0.5, 94.6 2.74, 105.52 7.23 M82.55 -4.66 C88.41 -2.58, 92.29 0.51, 106.21 5.41" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg>
+4 -2
src/lib/cards/CardScrollArea.svelte
··· 37 37 'h-[200px] w-[350px] rounded-2xl border border-base-200 dark:border-base-800 text-base-900 dark:text-base-100 bg-base-100 dark:bg-base-900'; 38 38 </script> 39 39 40 - <ScrollArea type="always" class={"h-full w-full text-base-900 dark:text-base-100 px-2 pt-0"} tabindex={-1}> 40 + <div class={"h-full w-full text-base-900 dark:text-base-100 px-2 pt-0 overflow-hidden relative"}> 41 41 <div class="p-4"> 42 42 {content} 43 43 </div> 44 - </ScrollArea> 44 + 45 + <div class="absolute top-2 right-2 w-1.5 h-16 rounded-full bg-base-200 dark:bg-base-800"></div> 46 + </div>
+244
src/lib/docs/Cards.svelte
··· 1 + <script> 2 + import { base } from '$app/paths'; 3 + import CardAlerts from '$lib/cards/CardAlerts.svelte'; 4 + import CardAvatar from '$lib/cards/CardAvatar.svelte'; 5 + import CardBadge from '$lib/cards/CardBadge.svelte'; 6 + import CardBox from '$lib/cards/CardBox.svelte'; 7 + import CardButtons from '$lib/cards/CardButtons.svelte'; 8 + import CardCard from '$lib/cards/CardCard.svelte'; 9 + import CardChatBubble from '$lib/cards/CardChatBubble.svelte'; 10 + import CardCheckbox from '$lib/cards/CardCheckbox.svelte'; 11 + import CardGithubCorner from '$lib/cards/CardGithubCorner.svelte'; 12 + import CardHead from '$lib/cards/CardHead.svelte'; 13 + import CardImage from '$lib/cards/CardImage.svelte'; 14 + import CardInput from '$lib/cards/CardInput.svelte'; 15 + import CardModal from '$lib/cards/CardModal.svelte'; 16 + import CardNumberInput from '$lib/cards/CardNumberInput.svelte'; 17 + import CardScrollArea from '$lib/cards/CardScrollArea.svelte'; 18 + import CardSlider from '$lib/cards/CardSlider.svelte'; 19 + import CardSonner from '$lib/cards/CardSonner.svelte'; 20 + import CardSwitch from '$lib/cards/CardSwitch.svelte'; 21 + import CardTextarea from '$lib/cards/CardTextarea.svelte'; 22 + import CardTooltip from '$lib/cards/CardTooltip.svelte'; 23 + import CardHeatmap from '$lib/cards/graphs/CardHeatmap.svelte'; 24 + import CardLineGraph from '$lib/cards/graphs/CardLineGraph.svelte'; 25 + import CardRingChart from '$lib/cards/graphs/CardRingChart.svelte'; 26 + import { cn } from '$lib/utils'; 27 + 28 + let baseCards = [ 29 + { 30 + component: CardAlerts, 31 + className: '', 32 + label: 'Alerts', 33 + href: '/alert' 34 + }, 35 + { 36 + component: CardAvatar, 37 + className: '', 38 + label: 'Avatars', 39 + href: '/avatar' 40 + }, 41 + { 42 + component: CardBadge, 43 + className: '', 44 + label: 'Badges', 45 + href: '/badge' 46 + }, 47 + { 48 + component: CardBox, 49 + className: '', 50 + label: 'Boxes', 51 + href: '/box' 52 + }, 53 + { 54 + component: CardButtons, 55 + className: '', 56 + label: 'Buttons', 57 + href: '/button' 58 + }, 59 + { 60 + component: CardChatBubble, 61 + className: '', 62 + label: 'Chat Bubbles', 63 + href: '/chat-bubble' 64 + }, 65 + { 66 + component: CardCheckbox, 67 + className: '', 68 + label: 'Checkboxes', 69 + href: '/checkbox' 70 + }, 71 + { 72 + component: CardImage, 73 + className: '', 74 + label: 'Images', 75 + href: '/image' 76 + }, 77 + { 78 + component: CardInput, 79 + className: '', 80 + label: 'Inputs', 81 + href: '/input' 82 + }, 83 + { 84 + component: CardModal, 85 + className: '', 86 + label: 'Modals', 87 + href: '/modal' 88 + }, 89 + { 90 + component: CardNumberInput, 91 + className: '', 92 + label: 'Number Inputs', 93 + href: '/number-input' 94 + }, 95 + { 96 + component: CardScrollArea, 97 + className: 'p-2', 98 + label: 'Scroll Areas', 99 + href: '/scroll-area' 100 + }, 101 + { 102 + component: CardSlider, 103 + className: '', 104 + label: 'Sliders', 105 + href: '/slider' 106 + }, 107 + { 108 + component: CardSonner, 109 + className: '', 110 + label: 'Sonner', 111 + href: '/sonner' 112 + }, 113 + { 114 + component: CardSwitch, 115 + className: '', 116 + label: 'Switches', 117 + href: '/switch' 118 + }, 119 + { 120 + component: CardTextarea, 121 + className: '', 122 + label: 'Textareas', 123 + href: '/textarea' 124 + }, 125 + { 126 + component: CardTooltip, 127 + className: '', 128 + label: 'Tooltips', 129 + href: '/tooltip' 130 + }, 131 + { 132 + component: CardCard, 133 + className: '', 134 + label: 'Cards', 135 + href: '/cards' 136 + }, 137 + { 138 + component: CardHead, 139 + className: 'px-0 gap-0', 140 + label: 'Head', 141 + href: '/head' 142 + } 143 + ]; 144 + 145 + // sort cards by label 146 + baseCards.sort((a, b) => a.label.localeCompare(b.label)); 147 + 148 + console.log('cards', baseCards.length); 149 + 150 + let graphCards = [ 151 + { 152 + component: CardHeatmap, 153 + className: '', 154 + label: 'Heatmap', 155 + href: '/heatmap' 156 + }, 157 + { 158 + component: CardLineGraph, 159 + className: '', 160 + label: 'Line Graph', 161 + href: '/line-graph' 162 + }, 163 + { 164 + component: CardRingChart, 165 + className: '', 166 + label: 'Ring Chart', 167 + href: '/ring-chart' 168 + } 169 + ]; 170 + 171 + // sort graphCards by label 172 + graphCards.sort((a, b) => a.label.localeCompare(b.label)); 173 + 174 + let socialCards = [ 175 + { 176 + component: CardGithubCorner, 177 + className: '', 178 + label: 'Github Corner', 179 + href: '/github-corner' 180 + } 181 + ]; 182 + </script> 183 + 184 + <h2 class="text-base-800 dark:text-base-200 mb-4 text-xl font-medium">Base components</h2> 185 + <div class="grid w-full grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 md:gap-8 lg:grid-cols-4"> 186 + {#each baseCards as card} 187 + <div 188 + class="group relative flex flex-col items-start gap-3 transition-opacity duration-150 hover:opacity-90 md:gap-4" 189 + > 190 + <div 191 + class="bg-base-100 dark:bg-base-900/30 border-base-200 dark:border-base-900 pointer-events-none relative h-44 w-full overflow-hidden rounded-2xl border" 192 + > 193 + <div 194 + class={cn( 195 + 'absolute inset-0 flex h-full w-full flex-col items-center justify-center gap-2 p-6 transition-transform duration-300 will-change-transform group-hover:scale-103', 196 + card.className 197 + )} 198 + aria-hidden="true" 199 + tabindex="-1" 200 + > 201 + <card.component /> 202 + </div> 203 + </div> 204 + <a 205 + href={base + `/components${card.href}`} 206 + class="focus-visible:outline-accent-500 rounded-xl focus-visible:outline-2 focus-visible:outline-offset-2" 207 + > 208 + <span class="text-base-900 dark:text-base-50 mx-2 text-sm font-semibold">{card.label}</span> 209 + <span class="absolute inset-0"></span> 210 + </a> 211 + </div> 212 + {/each} 213 + </div> 214 + 215 + <h2 class="text-base-800 dark:text-base-200 mt-24 mb-4 text-xl font-medium">Graph components</h2> 216 + <div class="grid w-full grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 md:gap-8 lg:grid-cols-4"> 217 + {#each graphCards as card} 218 + <div 219 + class="group relative flex flex-col items-start gap-3 transition-opacity duration-150 hover:opacity-90 md:gap-4" 220 + > 221 + <div 222 + class="bg-base-100 dark:bg-base-900/30 border-base-200 dark:border-base-900 pointer-events-none relative h-44 w-full overflow-hidden rounded-2xl border" 223 + > 224 + <div 225 + class={cn( 226 + 'absolute inset-0 flex h-full w-full flex-col items-center justify-center gap-2 p-6 transition-transform duration-300 will-change-transform group-hover:scale-103', 227 + card.className 228 + )} 229 + aria-hidden="true" 230 + tabindex="-1" 231 + > 232 + <card.component /> 233 + </div> 234 + </div> 235 + <a 236 + href={base + `/components${card.href}`} 237 + class="focus-visible:outline-accent-500 rounded-xl focus-visible:outline-2 focus-visible:outline-offset-2" 238 + > 239 + <span class="text-base-900 dark:text-base-50 mx-2 text-sm font-semibold">{card.label}</span> 240 + <span class="absolute inset-0"></span> 241 + </a> 242 + </div> 243 + {/each} 244 + </div>
+39
src/lib/docs/Navbar.svelte
··· 1 + <script lang="ts"> 2 + import { Button } from '$lib/components/base/button'; 3 + import { ThemeToggle } from '$lib/components/base/theme-toggle'; 4 + import Navbar from '$lib/components/base/navbar/Navbar.svelte'; 5 + 6 + import Github from '$lib/components/extra/social-icons/Github.svelte'; 7 + import Logo from '$lib/docs/Logo.svelte'; 8 + import ThemeSelectDropdown from '$lib/docs/ThemeSelectDropdown.svelte'; 9 + 10 + let { hasSidebar = false } = $props(); 11 + </script> 12 + 13 + 14 + <Navbar {hasSidebar}> 15 + <div class="flex items-center gap-2"> 16 + <Button variant="link" class="block font-normal lg:hidden" popovertarget="mobile-menu"> 17 + <svg 18 + xmlns="http://www.w3.org/2000/svg" 19 + viewBox="0 0 24 24" 20 + fill="currentColor" 21 + class="size-6!" 22 + > 23 + <path 24 + fill-rule="evenodd" 25 + d="M3 9a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 9Zm0 6.75a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z" 26 + clip-rule="evenodd" 27 + ></path> 28 + </svg> 29 + </Button> 30 + 31 + <Logo /> 32 + </div> 33 + <div class="flex items-end gap-4"> 34 + <ThemeSelectDropdown /> 35 + <ThemeToggle /> 36 + 37 + <Github href="https://github.com/flo-bit/ui-kit" class="py-2 pl-2 pr-4" /> 38 + </div> 39 + </Navbar>
+84
src/lib/docs/Sidebar.svelte
··· 1 + <script lang="ts"> 2 + import { base } from '$app/paths'; 3 + import { Accordion, AccordionItem } from '$lib/components/base/accordion'; 4 + 5 + import { Button } from '$lib/components/base/button'; 6 + import Sidebar from '$lib/components/base/sidebar/Sidebar.svelte'; 7 + 8 + const baseComponents = [ 9 + { label: 'Alerts', href: 'alert' }, 10 + { label: 'Avatars', href: 'avatar' }, 11 + { label: 'Badges', href: 'badge' }, 12 + { label: 'Box', href: 'box' }, 13 + { label: 'Buttons', href: 'button' }, 14 + { label: 'Chat Bubble', href: 'chat-bubble' }, 15 + { label: 'Checkboxes', href: 'checkbox' }, 16 + { label: 'Image', href: 'image' }, 17 + { label: 'Input', href: 'input' }, 18 + { label: 'Scroll Area', href: 'scroll-area' }, 19 + { label: 'Slider', href: 'slider' }, 20 + { label: 'Switch', href: 'switch' }, 21 + { label: 'Textarea', href: 'textarea' }, 22 + { label: 'Tooltip', href: 'tooltip' }, 23 + { label: 'Modal', href: 'modal' }, 24 + { label: 'Number Input', href: 'number-input' }, 25 + { label: 'Cards', href: 'cards' }, 26 + { label: 'Accordion', href: 'accordion' }, 27 + { label: 'Sonner', href: 'sonner' } 28 + ]; 29 + 30 + baseComponents.sort((a, b) => a.label.localeCompare(b.label)); 31 + console.log('components', baseComponents.length); 32 + 33 + const components = [ 34 + { label: 'Select', href: 'select' }, 35 + { label: 'Color Select', href: 'color-select' }, 36 + { label: 'Depth 3D', href: 'depth-3d' }, 37 + { label: 'Excalidraw', href: 'excalidraw' }, 38 + { label: 'Github Corner', href: 'github-corner' }, 39 + { label: 'Gradient', href: 'gradient' }, 40 + { label: 'Heatmap', href: 'heatmap' }, 41 + { label: 'Line Graph', href: 'line-graph' }, 42 + { label: 'Phone', href: 'phone' }, 43 + { label: 'Progress', href: 'progress' }, 44 + { label: 'Quote', href: 'quote' }, 45 + { label: 'Ring Chart', href: 'ring-chart' }, 46 + { label: 'Star Rating', href: 'star-rating' }, 47 + { label: 'Video Player', href: 'video-player' }, 48 + { label: 'Timer', href: 'timer' }, 49 + { label: 'Social Icons', href: 'social-icons' }, 50 + { label: 'Waveform Audio Player', href: 'waveform-audio-player' }, 51 + { label: 'Post', href: 'post' }, 52 + { label: 'Voxel Art', href: 'voxel-art' }, 53 + { label: 'Following Pointer', href: 'following-pointer' }, 54 + { label: 'Audio Visualizer', href: 'audio-visualizer' }, 55 + { label: 'Color Picker', href: 'color-picker' } 56 + ]; 57 + 58 + // sort components by label 59 + components.sort((a, b) => a.label.localeCompare(b.label)); 60 + 61 + let { mobileOnly = false } = $props(); 62 + </script> 63 + 64 + <Sidebar {mobileOnly}> 65 + <div class="mb-8 flex flex-col items-start p-4"> 66 + <Button variant="link" href={base}>Home</Button> 67 + <Button variant="link" href="{base}/components">All</Button> 68 + <Button variant="link" href="{base}/components/theme" class="mb-8">Theme</Button> 69 + 70 + <script lang="ts"> 71 + import { Accordion, AccordionItem } from '$lib/components/base/accordion'; 72 + import Subheading from '$lib/components/base/heading/Subheading.svelte'; 73 + </script> 74 + 75 + <Accordion type="single" class="w-full" value="base-components"> 76 + <AccordionItem value="base-components" title="Base Components" triggerClasses="text-sm px-3 font-semibold" contentClasses="flex flex-col gap-1 items-start px-2"> 77 + {#each baseComponents as component} 78 + <Button variant="ghost" href="{base}/components/{component.href}" class="w-full justify-start">{component.label}</Button 79 + > 80 + {/each} 81 + </AccordionItem> 82 + </Accordion> 83 + </div> 84 + </Sidebar>
+26
src/lib/docs/ThemeSelectDropdown.svelte
··· 1 + <script> 2 + import { buttonVariants } from '$lib/components/base/button'; 3 + import * as Popover from '$lib/components/base/popover'; 4 + import SelectTheme from '$lib/preview/SelectTheme.svelte'; 5 + import { cn } from '$lib/utils'; 6 + </script> 7 + 8 + <Popover.Root> 9 + <Popover.Trigger 10 + class={cn( 11 + buttonVariants({ variant: 'link', size: 'default' }), 12 + 'flex cursor-pointer items-center gap-0 -space-x-2' 13 + )} 14 + > 15 + <div 16 + class=" from-accent-500 to-accent-600 border-accent-700 dark:border-accent-400 z-10 size-6 rounded-full border bg-gradient-to-b" 17 + ></div> 18 + 19 + <div 20 + class=" from-base-500 to-base-600 border-base-700 dark:border-base-400 size-6 rounded-full border bg-gradient-to-b" 21 + ></div> 22 + </Popover.Trigger> 23 + <Popover.Content class="w-72"> 24 + <SelectTheme showTitle={false} /> 25 + </Popover.Content> 26 + </Popover.Root>
+9 -11
src/lib/preview/AccordionPreview.svelte
··· 1 1 <script lang="ts"> 2 - import * as Accordion from '$lib/components/base/accordion'; 2 + import { Accordion, AccordionItem } from '$lib/components/base/accordion'; 3 3 import Subheading from '$lib/components/base/heading/Subheading.svelte'; 4 4 </script> 5 5 6 6 <Subheading>Accordion</Subheading> 7 7 8 - <Accordion.Root type="single" class="w-full"> 9 - <Accordion.Item value="item-1"> 10 - <Accordion.Trigger>Is it accessible?</Accordion.Trigger> 11 - <Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content> 12 - </Accordion.Item> 13 - <Accordion.Item value="item-2"> 14 - <Accordion.Trigger>Is it accessible?</Accordion.Trigger> 15 - <Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content> 16 - </Accordion.Item> 17 - </Accordion.Root> 8 + <Accordion type="single" class="w-full"> 9 + <AccordionItem value="item-1" title="Is it accessible?"> 10 + Yes. It adheres to the WAI-ARIA design pattern. 11 + </AccordionItem> 12 + <AccordionItem value="item-2" title="Is it accessible?"> 13 + Yes. It adheres to the WAI-ARIA design pattern. 14 + </AccordionItem> 15 + </Accordion>
+51
src/lib/preview/ColorPickerPreview.svelte
··· 1 + <script lang="ts"> 2 + import { ColorPicker } from '$lib/components/extra/color-picker/base'; 3 + import * as Popover from '$lib/components/base/popover'; 4 + import { cn } from '$lib/utils'; 5 + import Subheading from '$lib/components/base/heading/Subheading.svelte'; 6 + import Button from '$lib/components/base/button/Button.svelte'; 7 + import type { SetColorFunction } from '$lib/components/extra/color-picker/base/color'; 8 + 9 + let setColor: SetColorFunction | undefined = $state(undefined); 10 + 11 + let okchl = $state({ 12 + l: 0, 13 + c: 0, 14 + h: 0 15 + }); 16 + 17 + let okhsv = $state({ 18 + h: 0, 19 + s: 0, 20 + v: 0 21 + }); 22 + 23 + $inspect(okchl); 24 + </script> 25 + 26 + <Subheading class="mb-4">Color Picker</Subheading> 27 + 28 + <Popover.Root> 29 + <Popover.Trigger class={cn('cursor-pointer')}> 30 + <div 31 + class="border-base-300 dark:border-base-700 z-10 size-8 rounded-full border" 32 + style={`background-color: oklch(${okchl.l} ${okchl.c} ${okchl.h});`} 33 + ></div> 34 + </Popover.Trigger> 35 + <Popover.Content side="bottom" sideOffset={10}> 36 + <ColorPicker 37 + bind:setColor 38 + onchange={(color) => { 39 + console.log(color); 40 + okchl = color.oklch; 41 + }} 42 + bind:okhsv 43 + /> 44 + </Popover.Content> 45 + </Popover.Root> 46 + 47 + <Button 48 + onclick={() => { 49 + setColor?.('#ff0000'); 50 + }}>Reset Color</Button 51 + >
+1 -1
src/lib/preview/ExcalidrawPreview.svelte
··· 2 2 import Excalidraw from '$lib/components/extra/excalidraw/Excalidraw.svelte'; 3 3 import Heading from '$lib/components/base/heading/Heading.svelte'; 4 4 5 - import svg from './assets/demo.svg?raw'; 5 + import svg from '$lib/assets/demo.svg?raw'; 6 6 </script> 7 7 8 8 <Heading class="mb-4">Excalidraw</Heading>
+4 -116
src/routes/components/+layout.svelte
··· 1 1 <script lang="ts"> 2 - import { base } from '$app/paths'; 3 - 4 - import { Button, buttonVariants } from '$lib/components/base/button'; 5 - import { ThemeToggle } from '$lib/components/base/theme-toggle'; 6 - import Navbar from '$lib/components/base/navbar/Navbar.svelte'; 7 - import Sidebar from '$lib/components/base/sidebar/Sidebar.svelte'; 2 + import Navbar from '$lib/docs/Navbar.svelte'; 3 + import Sidebar from '$lib/docs/Sidebar.svelte'; 8 4 import Container from '$lib/preview/Container.svelte'; 9 5 10 6 let { children } = $props(); 11 - 12 - import * as Popover from '$lib/components/base/popover'; 13 - import SelectTheme from '$lib/preview/SelectTheme.svelte'; 14 - import { cn } from '$lib/utils'; 15 - import Github from '$lib/components/extra/social-icons/Github.svelte'; 16 - import Logo from '$lib/docs/Logo.svelte'; 17 - 18 - const components = [ 19 - { label: 'Alerts', href: 'alert' }, 20 - { label: 'Avatars', href: 'avatar' }, 21 - { label: 'Badges', href: 'badge' }, 22 - { label: 'Box', href: 'box' }, 23 - { label: 'Buttons', href: 'button' }, 24 - { label: 'Chat Bubble', href: 'chat-bubble' }, 25 - { label: 'Checkboxes', href: 'checkbox' }, 26 - { label: 'Color Select', href: 'color-select' }, 27 - { label: 'Depth 3D', href: 'depth-3d' }, 28 - { label: 'Excalidraw', href: 'excalidraw' }, 29 - { label: 'Github Corner', href: 'github-corner' }, 30 - { label: 'Gradient', href: 'gradient' }, 31 - { label: 'Heatmap', href: 'heatmap' }, 32 - { label: 'Image', href: 'image' }, 33 - { label: 'Input', href: 'input' }, 34 - { label: 'Line Graph', href: 'line-graph' }, 35 - { label: 'Phone', href: 'phone' }, 36 - { label: 'Progress', href: 'progress' }, 37 - { label: 'Quote', href: 'quote' }, 38 - { label: 'Ring Chart', href: 'ring-chart' }, 39 - { label: 'Scroll Area', href: 'scroll-area' }, 40 - { label: 'Select', href: 'select' }, 41 - { label: 'Slider', href: 'slider' }, 42 - { label: 'Sonner', href: 'sonner' }, 43 - { label: 'Star Rating', href: 'star-rating' }, 44 - { label: 'Switch', href: 'switch' }, 45 - { label: 'Textarea', href: 'textarea' }, 46 - { label: 'Tooltip', href: 'tooltip' }, 47 - { label: 'Video Player', href: 'video-player' }, 48 - { label: 'Modal', href: 'modal' }, 49 - { label: 'Timer', href: 'timer' }, 50 - { label: 'Number Input', href: 'number-input' }, 51 - { label: 'Social Icons', href: 'social-icons' }, 52 - { label: 'Waveform Audio Player', href: 'waveform-audio-player' }, 53 - { label: 'Image Card', href: 'image-card' }, 54 - { label: 'Post', href: 'post' }, 55 - { label: 'Voxel Art', href: 'voxel-art' }, 56 - { label: 'Following Pointer', href: 'following-pointer' }, 57 - { label: 'Audio Visualizer', href: 'audio-visualizer' }, 58 - { label: 'Accordion', href: 'accordion' } 59 - ]; 60 - 61 - // sort components by label 62 - components.sort((a, b) => a.label.localeCompare(b.label)); 63 - 64 - console.log(components.length); 65 7 </script> 66 8 67 - <Navbar hasSidebar> 68 - <div class="flex items-center gap-2"> 69 - <Button variant="link" class="block font-normal lg:hidden" popovertarget="mobile-menu"> 70 - <svg 71 - xmlns="http://www.w3.org/2000/svg" 72 - viewBox="0 0 24 24" 73 - fill="currentColor" 74 - class="size-6!" 75 - > 76 - <path 77 - fill-rule="evenodd" 78 - d="M3 9a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 9Zm0 6.75a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z" 79 - clip-rule="evenodd" 80 - ></path> 81 - </svg> 82 - </Button> 83 - 84 - <Logo /> 85 - </div> 86 - <div class="flex items-end gap-4"> 87 - <Popover.Root> 88 - <Popover.Trigger 89 - class={cn( 90 - buttonVariants({ variant: 'link', size: 'default' }), 91 - 'flex cursor-pointer items-center gap-0 -space-x-2' 92 - )} 93 - > 94 - <div 95 - class=" from-accent-500 to-accent-600 border-accent-700 dark:border-accent-400 z-10 size-6 rounded-full border bg-gradient-to-b" 96 - ></div> 97 - 98 - <div 99 - class=" from-base-500 to-base-600 border-base-700 dark:border-base-400 size-6 rounded-full border bg-gradient-to-b" 100 - ></div> 101 - </Popover.Trigger> 102 - <Popover.Content> 103 - <SelectTheme showTitle={false} /> 104 - </Popover.Content> 105 - </Popover.Root> 106 - <ThemeToggle /> 107 - 108 - <Github href="https://github.com/flo-bit/ui-kit" class="py-2 pl-2 pr-4" /> 109 - </div> 110 - </Navbar> 9 + <Navbar hasSidebar /> 10 + <Sidebar /> 111 11 112 12 <Container> 113 13 {@render children()} 114 14 </Container> 115 - 116 - <Sidebar> 117 - <div class="flex flex-col items-start p-4 mb-8"> 118 - <Button variant="link" href={base}>Home</Button> 119 - <Button variant="link" href="{base}/components">All</Button> 120 - <Button variant="link" href="{base}/components/theme" class="mb-8">Theme</Button> 121 - 122 - {#each components as component} 123 - <Button variant="link" href="{base}/components/{component.href}">{component.label}</Button> 124 - {/each} 125 - </div> 126 - </Sidebar>
-2
src/lib/preview/assets/demo.svg
··· 1 - <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1098.1581271772438 257.514568002161" width="1098.1581271772438" height="257.514568002161" filter="invert(93%) hue-rotate(180deg)"><!-- svg-source:excalidraw --><metadata></metadata><defs><style class="style-fonts"> 2 - @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }</style></defs><g stroke-linecap="round" transform="translate(10 66.78471109388693) rotate(0 135.6523653909527 70.56376046809953)"><path d="M2.36 1.01 C62.02 0.61, 129.56 0.81, 271.55 0.7 M-0.28 -0.3 C74.51 2.34, 152.38 2.46, 271.81 -1.51 M269.82 -3.16 C268 53.81, 267.38 100.6, 271.78 137.65 M272.28 -1.06 C272.55 33.25, 272.49 64.42, 273.3 142.13 M270.6 139.4 C182.56 137.36, 91.46 139.85, 1.81 142.58 M272.4 140.28 C208.45 140.81, 148.3 140.54, 1.37 140.02 M2.02 138.57 C4.78 104.07, 3.76 69.39, -3.98 3.69 M0.05 141.42 C0.16 96.56, -0.37 48.86, -0.53 0.42" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(15.926413242515082 92.34847156198657) rotate(0 129.7259521484375 45)"><text x="129.7259521484375" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Create a graph</text><text x="129.7259521484375" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">in Excalidraw</text></g><g stroke-linecap="round" transform="translate(815.8202439362449 39.11611475625432) rotate(0 136.16894162049948 102.5)"><path d="M141.45 -1.23 C155.91 -2.36, 170.51 3.94, 184.58 8.62 C198.66 13.31, 214.23 19.12, 225.91 26.88 C237.59 34.64, 246.89 44.84, 254.67 55.18 C262.45 65.51, 270.06 77.6, 272.58 88.89 C275.1 100.18, 273.43 111.79, 269.78 122.93 C266.12 134.07, 258.71 145.68, 250.63 155.71 C242.55 165.74, 233.23 175.99, 221.28 183.09 C209.32 190.18, 193.98 194.51, 178.93 198.28 C163.88 202.05, 146.8 205.97, 130.97 205.69 C115.14 205.41, 98.04 201.1, 83.94 196.59 C69.84 192.09, 57.41 186.21, 46.38 178.65 C35.35 171.09, 24.91 161.24, 17.74 151.23 C10.58 141.22, 5.72 130.31, 3.37 118.58 C1.02 106.86, 0.65 92.79, 3.62 80.89 C6.59 68.99, 13.35 56.95, 21.22 47.18 C29.09 37.4, 38.55 29.5, 50.83 22.24 C63.12 14.99, 71.01 5.7, 94.91 3.64 C118.82 1.58, 171.2 4.67, 194.26 9.89 C217.32 15.1, 234.22 33.27, 233.26 34.95 M203.6 12.5 C218.33 17.03, 232.15 27.6, 241.88 36.35 C251.61 45.1, 257.25 53.87, 261.99 65.01 C266.74 76.15, 270.04 91.42, 270.37 103.2 C270.7 114.99, 269.27 125.2, 263.98 135.72 C258.69 146.24, 248.3 156.67, 238.63 166.3 C228.96 175.93, 219.18 187.64, 205.96 193.49 C192.74 199.35, 174.58 200.09, 159.32 201.42 C144.05 202.76, 129.48 202.91, 114.35 201.49 C99.22 200.08, 81.65 198.49, 68.54 192.92 C55.42 187.34, 45.2 177.1, 35.66 168.04 C26.12 158.97, 16.99 149.14, 11.29 138.53 C5.58 127.92, 2.25 116.17, 1.43 104.4 C0.6 92.62, 1.31 78.9, 6.32 67.88 C11.32 56.86, 21.53 46.96, 31.47 38.28 C41.4 29.6, 52.53 21.77, 65.9 15.8 C79.28 9.82, 96.58 4.4, 111.7 2.43 C126.82 0.46, 141.25 2.51, 156.62 3.97 C172 5.42, 196.48 8.95, 203.95 11.17 C211.42 13.38, 202.23 15.24, 201.45 17.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(861.3752311377848 96.63766968463324) rotate(0 90.82797241210938 45)"><text x="90.82797241210938" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Use as a</text><text x="90.82797241210938" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">component</text></g><g stroke-linecap="round" transform="translate(414.3162037907905 10) rotate(0 144.20211635590613 118.7572840010805)"><path d="M144.57 -1.12 C174.79 28.1, 207.41 49.63, 289.34 117.32 M145.56 -0.38 C180.67 26.31, 214.82 57.02, 287.26 119.16 M289.43 122.78 C259.71 139.8, 232.66 168.59, 148.87 235.79 M288.18 117.29 C251.8 146.89, 218.34 175.86, 143.52 239.13 M147.41 233.95 C111.22 210.22, 79.78 182.55, -2.18 118.24 M143.29 237.23 C99.77 203.03, 57.43 169.51, 1.94 118.46 M-1.39 117.43 C37.07 92.31, 79.74 55.81, 145 0.92 M-1.47 117.83 C47.61 77.49, 99.41 38.32, 143.32 0.17" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(495.5432843076105 83.87864200054037) rotate(0 62.87397766113281 45)"><text x="62.87397766113281" y="31.716" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Export</text><text x="62.87397766113281" y="76.71600000000001" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="36px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">as SVG</text></g><g stroke-linecap="round"><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M-1.57 1.67 C17.35 1.51, 94.72 -0.86, 114.68 -1.33 M2.78 0.11 C22.41 0.44, 100.62 1.89, 119.64 2.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M97.2 8.44 C103.97 8.32, 112.2 5.39, 120.1 1.21 M96.34 10.89 C100.25 8.35, 106.12 6.14, 119.97 3.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(289.9345924990341 128.0942984658334) rotate(0 58.66644832232794 -0.5622745147453543)"><path d="M97.5 -8.66 C104.15 -2.92, 112.27 0.01, 120.1 1.21 M96.64 -6.21 C100.34 -5.04, 106.14 -3.53, 119.97 3.19" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M-0.2 1.49 C17.61 1.93, 88.56 2.77, 106.41 3.26 M-3.76 -0.17 C13.94 0.49, 87.03 4.4, 105.42 5.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M82.54 14.41 C86.51 12.75, 94.14 10.16, 105.52 7.23 M81.56 12.41 C87.56 10.69, 91.65 9.98, 106.21 5.41" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(709.944766750602 130.6514274457768) rotate(0 52.049792246114066 2.8372606677794465)"><path d="M83.53 -2.66 C87.26 0.5, 94.6 2.74, 105.52 7.23 M82.55 -4.66 C88.41 -2.58, 92.29 0.51, 106.21 5.41" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg>
+5
src/routes/components/cards/+page.svelte
··· 1 + <script> 2 + import ImageCardPreview from '$lib/preview/ImageCardPreview.svelte'; 3 + </script> 4 + 5 + <ImageCardPreview />
+6
src/routes/components/color-picker/+page.svelte
··· 1 + <script> 2 + import ColorPickerPreview from "$lib/preview/ColorPickerPreview.svelte"; 3 + 4 + </script> 5 + 6 + <ColorPickerPreview />
-5
src/routes/components/image-card/+page.svelte
··· 1 - <script> 2 - import ImageCardPreview from '$lib/preview/ImageCardPreview.svelte'; 3 - </script> 4 - 5 - <ImageCardPreview />
+2 -4
src/lib/components/base/accordion/accordion-content.svelte
··· 13 13 <AccordionPrimitive.Content 14 14 bind:ref 15 15 class={cn( 16 - 'text-base-800 dark:text-base-200 overflow-hidden text-sm transition-all mt-2', 16 + 'text-base-800 dark:text-base-200 pt-2 overflow-hidden text-sm transition-all pb-4', 17 17 className 18 18 )} 19 19 {...restProps} 20 20 > 21 - <div class="pt-0 pb-4"> 22 - {@render children?.()} 23 - </div> 21 + {@render children?.()} 24 22 </AccordionPrimitive.Content>
+27 -4
src/lib/components/base/accordion/accordion-item.svelte
··· 1 1 <script lang="ts"> 2 - import { Accordion as AccordionPrimitive } from "bits-ui"; 3 - import { cn } from "$lib/utils"; 2 + import { Accordion as AccordionPrimitive } from 'bits-ui'; 3 + import { cn } from '$lib/utils'; 4 + import AccordionContent from './accordion-content.svelte'; 5 + import { AccordionTrigger } from '.'; 4 6 5 7 let { 6 8 ref = $bindable(null), 7 9 class: className, 10 + title, 11 + children, 12 + contentClasses, 13 + triggerClasses, 8 14 ...restProps 9 - }: AccordionPrimitive.ItemProps = $props(); 15 + }: AccordionPrimitive.ItemProps & { 16 + title?: string; 17 + contentClasses?: string; 18 + triggerClasses?: string; 19 + } = $props(); 10 20 </script> 11 21 12 - <AccordionPrimitive.Item bind:ref class={cn("border-b border-base-200 dark:border-base-900", className)} {...restProps} /> 22 + <AccordionPrimitive.Item 23 + bind:ref 24 + class={cn('border-base-200 dark:border-base-900 border-b', className)} 25 + {...restProps} 26 + > 27 + {#if title} 28 + <AccordionTrigger class={triggerClasses}>{title}</AccordionTrigger> 29 + <AccordionContent class={contentClasses}> 30 + {@render children?.()} 31 + </AccordionContent> 32 + {:else} 33 + {@render children?.()} 34 + {/if} 35 + </AccordionPrimitive.Item>
+2 -1
src/lib/components/base/button/Button.svelte
··· 13 13 secondary: 14 14 'focus-visible:outline-base-800 dark:focus-visible:outline-base-200 bg-base-300/30 dark:bg-base-800/50 text-base-900 dark:text-base-50 hover:bg-base-300/50 dark:hover:bg-base-700/50 border border-base-300/50 dark:border-base-700/30', 15 15 link: 'focus-visible:outline-base-900 dark:focus-visible:outline-base-50 text-base-800 dark:text-base-200 font-semibold hover:text-accent-600 dark:hover:text-accent-400 data-[current=true]:text-accent-600 dark:data-[current=true]:text-accent-400', 16 + ghost: 'focus-visible:outline-base-900 dark:focus-visible:outline-base-50 text-base-800 dark:text-base-200 font-semibold hover:text-accent-600 hover:bg-base-400/5 data-[current=true]:bg-base-500/5 dark:hover:text-accent-400 dark:hover:bg-base-700/5 data-[current=true]:text-accent-600 dark:data-[current=true]:text-accent-400 dark:data-[current=true]:bg-base-500/5', 16 17 17 18 red: 'focus-visible:outline-red-500 border border-red-500/20 dark:border-red-500/20 hover:bg-red-500/20 dark:hover:bg-red-500/20 bg-red-500/10 dark:bg-red-500/10 text-red-700 dark:text-red-400', 18 19 yellow: ··· 86 87 {#if href} 87 88 <a 88 89 bind:this={ref} 89 - data-current={variant === 'link' && checkCurrent && page.url.pathname === href} 90 + data-current={checkCurrent && page.url.pathname === href} 90 91 class={cn(buttonVariants({ variant, size }), className)} 91 92 {href} 92 93 {...restProps}
+75 -16
src/lib/components/base/number-input/NumberInput.svelte
··· 1 - <script lang="ts"> 1 + <script lang="ts" module> 2 + import type { WithElementRef } from 'bits-ui'; 3 + import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; 4 + import { type VariantProps, tv } from 'tailwind-variants'; 2 5 import { cn } from '$lib/utils'; 6 + 7 + export const numberInputVariants = tv({ 8 + base: 'group flex w-full max-w-44 touch-manipulation items-stretch justify-between rounded-2xl ring focus-within:ring-2', 9 + variants: { 10 + variant: { 11 + primary: 12 + 'bg-accent-500/5 focus-within:ring-accent-400 dark:focus-within:ring-accent-600 text-accent-500 ring-accent-500/20 dark:ring-accent-500/20', 13 + secondary: 14 + '', 15 + }, 16 + size: { 17 + default: 'text-base font-medium max-w-32', 18 + sm: 'text-sm', 19 + lg: 'text-3xl font-semibold max-w-44', 20 + } 21 + }, 22 + defaultVariants: { 23 + variant: 'primary', 24 + size: 'default' 25 + } 26 + }); 27 + 28 + export const numberInputButtonVariants = tv({ 29 + base: 'button-number-input flex cursor-pointer items-center pr-[.5em] pl-[.5em] transition-colors disabled:cursor-not-allowed disabled:opacity-50', 30 + variants: { 31 + variant: { 32 + primary: 'hover:text-accent-600 dark:hover:text-accent-400 disabled:hover:text-accent-500 dark:disabled:hover:text-accent-500', 33 + secondary: 'hover:text-base-600 dark:hover:text-base-400 disabled:hover:text-base-500 dark:disabled:hover:text-base-500', 34 + }, 35 + }, 36 + }); 37 + 38 + export const numberInputInputVariants = tv({ 39 + base: 'number-input w-full [appearance:textfield] border-0 bg-transparent text-center font-[inherit] font-semibold text-transparent outline-none [-moz-appearance:_textfield] focus-visible:ring-0 [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none', 40 + variants: { 41 + variant: { 42 + primary: '', 43 + secondary: '', 44 + }, 45 + size: { 46 + default: 'p-1', 47 + sm: '', 48 + lg: 'p-2', 49 + } 50 + }, 51 + }); 52 + export type NumberInputVariant = VariantProps<typeof numberInputVariants>['variant']; 53 + export type NumberInputSize = VariantProps<typeof numberInputVariants>['size']; 54 + 55 + export type NumberInputProps = WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 56 + variant?: NumberInputVariant; 57 + size?: NumberInputSize; 58 + min?: number; 59 + value?: number; 60 + max?: number; 61 + defaultValue?: number; 62 + class?: string; 63 + step?: number; 64 + inputRef?: HTMLInputElement | null; 65 + }; 66 + </script> 67 + 68 + <script lang="ts"> 3 69 import NumberFlow from '@number-flow/svelte'; 4 - import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui'; 70 + import type { WithoutChildrenOrChild } from 'bits-ui'; 5 71 import type { HTMLAttributes } from 'svelte/elements'; 6 72 7 73 let { ··· 14 80 ref = $bindable(null), 15 81 inputRef = $bindable(null), 16 82 tabindex = undefined, 83 + variant = 'primary', 84 + size = 'default', 17 85 ...restProps 18 - }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & { 19 - min?: number; 20 - value?: number; 21 - max?: number; 22 - defaultValue?: number; 23 - class?: string; 24 - step?: number; 25 - inputRef?: HTMLInputElement | null; 26 - } = $props(); 86 + }: NumberInputProps = $props(); 27 87 28 88 let input: HTMLInputElement; 29 89 let animated = $state(true); ··· 54 114 55 115 <div 56 116 class={cn( 57 - 'bg-accent-500/5 focus-within:ring-accent-400 dark:focus-within:ring-accent-600 text-accent-500 group ring-accent-500/20 dark:ring-accent-500/20 flex w-full max-w-44 touch-manipulation items-stretch justify-between rounded-2xl text-3xl font-semibold ring transition-[box-shadow] focus-within:ring-2', 117 + numberInputVariants({ variant, size }), 58 118 className 59 119 )} 60 120 bind:this={ref} ··· 63 123 <button 64 124 aria-hidden="true" 65 125 tabindex={-1} 66 - class="button-number-input hover:text-accent-600 dark:hover:text-accent-400 disabled:hover:text-accent-500 dark:disabled:hover:text-accent-500 flex cursor-pointer items-center pr-[.325em] pl-[.5em] transition-colors disabled:cursor-not-allowed disabled:opacity-50" 126 + class={numberInputButtonVariants({ variant })} 67 127 disabled={min != null && value <= min} 68 128 onpointerdown={(event) => handlePointerDown(event, -step)} 69 129 > ··· 83 143 > 84 144 <input 85 145 class={cn( 86 - 'number-input', 146 + numberInputInputVariants({ variant, size }), 87 147 showCaret ? 'caret-accent-500' : 'caret-transparent', 88 - 'w-full [appearance:textfield] border-0 bg-transparent py-2 text-center font-[inherit] text-3xl font-semibold text-transparent outline-none [-moz-appearance:_textfield] focus-visible:ring-0 [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none' 89 148 )} 90 149 style="font-kerning: none" 91 150 type="number" ··· 114 173 <button 115 174 aria-hidden="true" 116 175 tabindex="-1" 117 - class="button-number-input hover:text-accent-600 dark:hover:text-accent-400 disabled:hover:text-accent-500 dark:disabled:hover:text-accent-500 flex cursor-pointer items-center pr-[.5em] pl-[.325em] transition-colors disabled:cursor-not-allowed disabled:opacity-50" 176 + class={numberInputButtonVariants({variant})} 118 177 disabled={max != null && value >= max} 119 178 onpointerdown={(event) => handlePointerDown(event, step)} 120 179 >
+1 -1
src/lib/components/base/popover/popover-content.svelte
··· 20 20 {sideOffset} 21 21 {align} 22 22 class={cn( 23 - 'bg-base-100 dark:bg-base-900 border-base-200 dark:border-base-800 text-base-900 dark:text-base-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-2xl border p-4 shadow-md outline-none', 23 + 'bg-base-100 dark:bg-base-900 border-base-200 dark:border-base-800 text-base-900 dark:text-base-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-2xl border p-4 shadow-md outline-none', 24 24 className 25 25 )} 26 26 {...restProps}
+14 -10
src/lib/components/base/sidebar/Sidebar.svelte
··· 8 8 class: className, 9 9 mobileClasses, 10 10 children, 11 + mobileOnly = false, 11 12 ...restProps 12 13 }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { 13 14 mobileClasses?: string; 15 + mobileOnly?: boolean; 14 16 } = $props(); 15 17 </script> 16 18 ··· 28 30 </ScrollArea> 29 31 </div> 30 32 31 - <div 32 - class={cn( 33 - 'border-base-200 bg-base-50 dark:border-base-300/10 dark:bg-base-950 fixed top-0 bottom-0 left-0 z-40 hidden h-full -translate-x-72 overflow-y-auto border-r lg:block lg:w-72 lg:translate-x-0 xl:w-80', 34 - className 35 - )} 36 - > 37 - <ScrollArea type="hover" class="h-full"> 38 - {@render children?.()} 39 - </ScrollArea> 40 - </div> 33 + {#if !mobileOnly} 34 + <div 35 + class={cn( 36 + 'border-base-200 bg-base-50 dark:border-base-300/10 dark:bg-base-950 fixed top-0 bottom-0 left-0 z-40 hidden h-full -translate-x-72 overflow-y-auto border-r lg:block lg:w-72 lg:translate-x-0 xl:w-80', 37 + className 38 + )} 39 + > 40 + <ScrollArea type="hover" class="h-full"> 41 + {@render children?.()} 42 + </ScrollArea> 43 + </div> 44 + {/if} 41 45 42 46 <style> 43 47 .mobileMenuPopover {
+2 -2
src/lib/components/base/sonner/Toaster.svelte
··· 57 57 unstyled: true, 58 58 classes: { 59 59 toast: 60 - 'group toast min-w-12 w-fit sm:min-w-64 backdrop-blur-lg border rounded-2xl p-4 flex items-center gap-2 sm:fixed sm:top-4 sm:right-0 mx-2', 60 + 'group toast min-w-12 w-fit sm:min-w-64 backdrop-blur-lg border rounded-2xl p-4 flex items-center gap-2 sm:fixed sm:bottom-4 sm:right-0 mx-2', 61 61 title: 'text-base title', 62 62 description: 'text-xs mt-1 description', 63 63 ··· 69 69 info: colorClasses[colors?.info ?? 'blue'] 70 70 } 71 71 }} 72 - position="top-right" 72 + position="bottom-right" 73 73 />
+276
src/lib/components/extra/color-picker/base/ColorPicker.svelte
··· 1 + <script lang="ts"> 2 + import { 3 + eps, 4 + picker_size, 5 + slider_width, 6 + border_size, 7 + gap_size, 8 + } from './constants' 9 + import { render_main_image, render_slider_image } from './render' 10 + import type { RGB, OKlab, OKhsv, OKlch } from './color' 11 + import { oklab_to_okhsv, okhsv_to_oklab, rgb_to_okhsv, okhsv_to_rgb, okhsv_to_oklch, rgb_to_hex } from './color' 12 + 13 + export let rgb: RGB | undefined = undefined 14 + export let oklab: OKlab | undefined = undefined 15 + export let okhsv: OKhsv | undefined = undefined 16 + 17 + export let onchange: (color: { hex: string, rgb: RGB, oklab: OKlab, okhsv: OKhsv, oklch: OKlch }) => void = () => {} 18 + 19 + const width = picker_size + slider_width + gap_size + border_size * 2 20 + const height = picker_size + border_size * 2 21 + 22 + $: color = convertToInternal(rgb, oklab, okhsv) 23 + $: uihsv = scale_to_ui(color) 24 + 25 + function scale_to_ui(okhsv: OKhsv): OKhsv { 26 + return { 27 + h: clamp_ui(okhsv.h / 360), 28 + s: clamp_ui(okhsv.s), 29 + v: clamp_ui(1 - okhsv.v), 30 + } 31 + } 32 + 33 + function clamp(x: number) { 34 + return x < eps ? eps : x > 1 - eps ? 1 - eps : x 35 + } 36 + 37 + function clamp_ui(value: number) { 38 + return value < 0 ? 0 : value > 1 ? picker_size : value * picker_size 39 + } 40 + 41 + function convertToInternal(rgb: RGB | undefined, oklab: OKlab | undefined, okhsv: OKhsv | undefined) { 42 + if (okhsv) { 43 + return okhsv 44 + } 45 + 46 + if (oklab) { 47 + return oklab_to_okhsv(oklab) 48 + } 49 + 50 + if (rgb) { 51 + return rgb_to_okhsv(rgb) 52 + } 53 + 54 + throw 'rgb, oklab, or okhsv required' 55 + } 56 + 57 + async function update_input() { 58 + let new_rgb = okhsv_to_rgb(color) 59 + let new_oklab = okhsv_to_oklab(color) 60 + let new_oklch = okhsv_to_oklch(color) 61 + let new_hex = rgb_to_hex(new_rgb) 62 + let new_okhsv = color 63 + 64 + if (okhsv) { 65 + okhsv = color 66 + } 67 + 68 + if (oklab) { 69 + oklab = new_oklab 70 + } 71 + 72 + if (rgb) { 73 + rgb = new_rgb 74 + } 75 + 76 + onchange({ 77 + rgb: new_rgb, 78 + oklab: new_oklab, 79 + okhsv: new_okhsv, 80 + oklch: new_oklch, 81 + hex: new_hex 82 + }) 83 + } 84 + 85 + function update_sv(x: number, y: number) { 86 + let new_s = clamp(x / picker_size) 87 + let new_v = clamp(1 - y / picker_size) 88 + 89 + color.s = new_s 90 + color.v = new_v 91 + 92 + update_input() 93 + } 94 + 95 + function update_h(x: number, y: number) { 96 + let h = clamp(y / picker_size) 97 + color.h = h * 360 98 + color.s = Math.max(color.s, 0.00001) 99 + color.v = Math.max(color.v, 0.00001) 100 + 101 + update_input() 102 + } 103 + 104 + function pointer( 105 + node: HTMLCanvasElement, 106 + fn: (x: number, y: number) => void 107 + ) { 108 + let active = false 109 + 110 + function update(event: PointerEvent) { 111 + const x = event.offsetX 112 + const y = event.offsetY 113 + fn(x, y) 114 + } 115 + 116 + function onpointerdown(event: PointerEvent) { 117 + event.stopPropagation() 118 + node.setPointerCapture(event.pointerId) 119 + update(event) 120 + active = true 121 + } 122 + 123 + function onpointermove(event: PointerEvent) { 124 + event.stopPropagation() 125 + if (active) { 126 + update(event) 127 + } 128 + } 129 + 130 + function onpointerend(event: PointerEvent) { 131 + event.stopPropagation() 132 + node.releasePointerCapture(event.pointerId) 133 + active = false 134 + } 135 + 136 + node.addEventListener('pointerdown', onpointerdown, { passive: true }) 137 + node.addEventListener('pointermove', onpointermove, { passive: true }) 138 + node.addEventListener('pointerup', onpointerend, { passive: true }) 139 + node.addEventListener('pointercancel', onpointerend, { passive: true }) 140 + 141 + return { 142 + destroy() { 143 + node.removeEventListener('pointerdown', onpointerdown) 144 + node.removeEventListener('pointermove', onpointermove) 145 + node.removeEventListener('pointerup', onpointerend) 146 + node.removeEventListener('pointercancel', onpointerend) 147 + }, 148 + } 149 + } 150 + 151 + function onKeydown(event: KeyboardEvent) { 152 + const keyHandled = () => { 153 + event.preventDefault() 154 + event.stopPropagation() 155 + } 156 + 157 + const step = event.shiftKey ? 10 : 1 158 + 159 + switch (event.key) { 160 + case 'ArrowUp': 161 + if (event.altKey) { 162 + update_h(0, Math.round(uihsv.h! - step)) 163 + } else { 164 + update_sv(uihsv.s, Math.round(uihsv.v - step)) 165 + } 166 + keyHandled() 167 + break 168 + 169 + case 'ArrowDown': 170 + if (event.altKey) { 171 + update_h(0, Math.round(uihsv.h! + step)) 172 + } else { 173 + update_sv(uihsv.s, Math.round(uihsv.v + step)) 174 + } 175 + keyHandled() 176 + break 177 + 178 + case 'ArrowLeft': 179 + update_sv(Math.round(uihsv.s - step), uihsv.v) 180 + keyHandled() 181 + break 182 + 183 + case 'ArrowRight': 184 + update_sv(Math.round(uihsv.s + step), uihsv.v) 185 + keyHandled() 186 + break 187 + } 188 + } 189 + </script> 190 + 191 + <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 192 + <!-- svelte-ignore a11y-no-static-element-interactions --> 193 + <div 194 + class={$$props.class} 195 + tabindex="0" 196 + style:width="{width}px" 197 + style:height="{height}px" 198 + on:keydown={onKeydown} 199 + > 200 + <canvas 201 + id="okhsv_sv_canvas" 202 + width={picker_size} 203 + height={picker_size} 204 + style:top="{border_size}px" 205 + style:left="{border_size}px" 206 + class="rounded-xl" 207 + use:pointer={update_sv} 208 + use:render_main_image={color.h} 209 + ></canvas> 210 + <canvas 211 + width={slider_width} 212 + height={picker_size} 213 + style:top="{border_size}px" 214 + style:left="{picker_size + gap_size}px" 215 + class="rounded-xl" 216 + use:pointer={update_h} 217 + use:render_slider_image 218 + ></canvas> 219 + 220 + <svg {width} {height}> 221 + <g transform="translate({border_size},{border_size})"> 222 + <g transform="translate({uihsv.s},{uihsv.v})"> 223 + <circle 224 + cx="0" 225 + cy="0" 226 + r="5" 227 + fill="none" 228 + stroke-width="1.75" 229 + stroke="#fff" 230 + /> 231 + <circle 232 + cx="0" 233 + cy="0" 234 + r="6" 235 + fill="none" 236 + stroke-width="1.25" 237 + stroke="#000" 238 + /> 239 + </g> 240 + </g> 241 + <g transform="translate({picker_size + gap_size},{border_size})"> 242 + <g transform="translate(0,{uihsv.h})"> 243 + <polygon 244 + points="-7,-4 -1,0 -7,4" 245 + fill="#fff" 246 + stroke-width="0.8" 247 + stroke="#000" 248 + /> 249 + <polygon 250 + points="{slider_width + 7},-4 {slider_width + 1},0 {slider_width + 251 + 7},4" 252 + fill="#fff" 253 + stroke-width="0.8" 254 + stroke="#000" 255 + /> 256 + </g> 257 + </g> 258 + </svg> 259 + </div> 260 + 261 + <style> 262 + div { 263 + position: relative; 264 + outline: 0; 265 + } 266 + 267 + canvas, 268 + svg { 269 + touch-action: none; 270 + position: absolute; 271 + } 272 + 273 + svg { 274 + pointer-events: none; 275 + } 276 + </style>
+108
src/lib/components/extra/color-picker/base/color.ts
··· 1 + import { convert, OKHSL, OKHSV, OKLab, OKLCH, sRGB } from '@texel/color'; 2 + 3 + export type Color = string | RGB | OKlab | OKhsv | OKlch; 4 + 5 + export type SetColorFunction = (color: Color) => void; 6 + 7 + export interface RGB { 8 + r: number; 9 + g: number; 10 + b: number; 11 + } 12 + 13 + export interface OKlab { 14 + l: number; 15 + a: number; 16 + b: number; 17 + } 18 + 19 + export interface OKhsv { 20 + h: number; 21 + s: number; 22 + v: number; 23 + } 24 + 25 + export interface OKhsl { 26 + h: number; 27 + s: number; 28 + l: number; 29 + } 30 + 31 + export interface OKlch { 32 + l: number; 33 + c: number; 34 + h: number; 35 + } 36 + 37 + export function okhsv_to_oklch(okhsv: OKhsv): OKlch { 38 + const [l, c, h] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, OKLCH); 39 + return { l: l, c: c, h: h }; 40 + } 41 + 42 + export function oklch_to_okhsv(oklch: OKlch): OKhsv { 43 + const [h, s, v] = convert([oklch.h, oklch.c, oklch.l], OKLCH, OKHSV); 44 + return { h: h ?? 0, s, v }; 45 + } 46 + 47 + export function oklab_to_okhsv(oklab: OKlab): OKhsv { 48 + const [h, s, v] = convert([oklab.l, oklab.a, oklab.b], OKLab, OKHSV); 49 + return { h: h ?? 0, s, v }; 50 + } 51 + 52 + export function okhsv_to_rgb(okhsv: OKhsv): RGB { 53 + const [r, g, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, sRGB); 54 + return { r, g, b }; 55 + } 56 + 57 + export function okhsv_to_oklab(okhsv: OKhsv): OKlab { 58 + const [l, a, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, OKLab); 59 + return { l, a, b }; 60 + } 61 + 62 + export function rgb_to_okhsv(rgb: RGB): OKhsv { 63 + const [h, s, v] = convert([rgb.r, rgb.g, rgb.b], sRGB, OKHSV); 64 + return { h, s, v }; 65 + } 66 + 67 + export function okhsl_to_rgb(okhsl: OKhsl): RGB { 68 + const [r, g, b] = convert([okhsl.h, okhsl.s, okhsl.l], OKHSL, sRGB); 69 + return { r, g, b }; 70 + } 71 + 72 + export function rgb_to_hex(rgb: RGB): string { 73 + const r = Math.round(rgb.r * 255); 74 + const g = Math.round(rgb.g * 255); 75 + const b = Math.round(rgb.b * 255); 76 + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; 77 + } 78 + 79 + export function hex_to_rgb(hex: string): RGB { 80 + if (hex.startsWith('#')) { 81 + hex = hex.slice(1); 82 + } else if (hex.startsWith('0x')) { 83 + hex = hex.slice(2); 84 + } 85 + 86 + if (hex.length !== 6) { 87 + console.warn('Invalid hex color', hex); 88 + return { r: 0, g: 0, b: 0 }; 89 + } 90 + 91 + const r = parseInt(hex.slice(0, 2), 16); 92 + const g = parseInt(hex.slice(2, 4), 16); 93 + const b = parseInt(hex.slice(4, 6), 16); 94 + const r_ = r / 255; 95 + const g_ = g / 255; 96 + const b_ = b / 255; 97 + return { r: r_, g: g_, b: b_ }; 98 + } 99 + 100 + export function hex_to_okhsv(hex: string): OKhsv { 101 + const rgb = hex_to_rgb(hex); 102 + return rgb_to_okhsv(rgb); 103 + } 104 + 105 + export function okhsv_to_hex(okhsv: OKhsv): string { 106 + const rgb = okhsv_to_rgb(okhsv); 107 + return rgb_to_hex(rgb); 108 + }
+5
src/lib/components/extra/color-picker/base/constants.ts
··· 1 + export const picker_size = 200; 2 + export const slider_width = 16; 3 + export const border_size = 10; 4 + export const gap_size = 30; 5 + export const eps = 0.0001;
+5
src/lib/components/extra/color-picker/base/index.ts
··· 1 + // adapted from https://github.com/CaptainCodeman/svelte-color-select 2 + 3 + export { default } from './ColorPicker.svelte'; 4 + export { default as ColorPicker } from './ColorPicker.svelte'; 5 + export type { RGB, OKlab, OKhsv } from './color';
+100
src/lib/components/extra/color-picker/base/render.ts
··· 1 + import { f_shader, v_shader } from './shaders' 2 + import { picker_size, slider_width } from './constants' 3 + import { okhsl_to_rgb } from './color' 4 + 5 + export function render_slider_image(canvas: HTMLCanvasElement) { 6 + const ctx = canvas.getContext('2d')! 7 + const data = new Uint8ClampedArray(picker_size * slider_width * 4) 8 + 9 + for (let i = 0; i < picker_size; i++) { 10 + const a_ = Math.cos(2 * Math.PI * i / picker_size) 11 + const b_ = Math.sin(2 * Math.PI * i / picker_size) 12 + 13 + const rgb = okhsl_to_rgb({ 14 + h: i / picker_size * 360, 15 + s: 0.9, 16 + l: 0.65 + 0.17 * b_ - 0.08 * a_, 17 + }) 18 + 19 + for (let j = 0; j < slider_width; j++) { 20 + const index = 4 * (i * slider_width + j) 21 + data[index + 0] = rgb.r * 255 22 + data[index + 1] = rgb.g * 255 23 + data[index + 2] = rgb.b * 255 24 + data[index + 3] = 255 25 + } 26 + } 27 + 28 + const imageData = new ImageData(data, slider_width) 29 + ctx.putImageData(imageData, 0, 0) 30 + } 31 + 32 + export function render_main_image(canvas: HTMLCanvasElement, hue: number) { 33 + const gl = canvas.getContext('webgl')! 34 + 35 + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) 36 + 37 + const shaderProgram = initShaderProgram(gl, v_shader, f_shader)! 38 + 39 + const position = gl.getAttribLocation(shaderProgram, "position") 40 + const resolution = gl.getUniformLocation(shaderProgram, "resolution") 41 + const hue_rad = gl.getUniformLocation(shaderProgram, "hue_rad") 42 + 43 + gl.useProgram(shaderProgram) 44 + gl.uniform2fv(resolution, [gl.canvas.width, gl.canvas.height]) 45 + 46 + const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0] 47 + const positionBuffer = gl.createBuffer() 48 + 49 + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer) 50 + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW) 51 + gl.enableVertexAttribArray(position) 52 + gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0) 53 + 54 + function update(hue: number) { 55 + requestAnimationFrame(() => { 56 + gl.uniform1f(hue_rad, hue / 360) 57 + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) 58 + }) 59 + } 60 + 61 + update(hue) 62 + 63 + return { 64 + update, 65 + destroy() { 66 + gl.deleteProgram(shaderProgram) 67 + } 68 + } 69 + } 70 + 71 + function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string) { 72 + const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)! 73 + const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)! 74 + 75 + const shaderProgram = gl.createProgram()! 76 + gl.attachShader(shaderProgram, vertexShader) 77 + gl.attachShader(shaderProgram, fragmentShader) 78 + gl.linkProgram(shaderProgram) 79 + 80 + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 81 + throw `unable to init shader: ${gl.getProgramInfoLog(shaderProgram)}` 82 + } 83 + 84 + return shaderProgram 85 + } 86 + 87 + function loadShader(gl: WebGLRenderingContext, type: number, source: string) { 88 + const shader = gl.createShader(type)! 89 + 90 + gl.shaderSource(shader, source) 91 + gl.compileShader(shader) 92 + 93 + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 94 + const info = gl.getShaderInfoLog(shader) 95 + gl.deleteShader(shader) 96 + throw `error compiling shaders: ${info}` 97 + } 98 + 99 + return shader 100 + }
+7
src/lib/components/extra/color-picker/base/shaders.ts
··· 1 + /// <reference types="vite-plugin-glsl/ext" /> 2 + 3 + import f_shader_src from './shaders/f_shader.glsl'; 4 + import v_shader_src from './shaders/v_shader.glsl'; 5 + 6 + export const f_shader = f_shader_src; 7 + export const v_shader = v_shader_src;
src/lib/components/extra/color-picker/popover/PopoverColorPicker.svelte
+16
src/lib/components/extra/color-picker/base/shaders/f_shader.glsl
··· 1 + precision mediump float; 2 + uniform vec2 resolution; 3 + uniform float hue_rad; 4 + 5 + #include library; 6 + 7 + void main() { 8 + vec2 uv = gl_FragCoord.xy / resolution; 9 + 10 + float l = uv.y; 11 + float h = hue_rad; 12 + 13 + vec3 hsl = vec3(h, uv.x, uv.y); 14 + vec3 hsvRGB = okhsv_to_srgb(hsl); 15 + gl_FragColor = vec4(hsvRGB, 1.0); 16 + }
+183
src/lib/components/extra/color-picker/base/shaders/library.glsl
··· 1 + // Copyright(c) 2021 Björn Ottosson 2 + // 3 + // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 + // this softwareand associated documentation files(the "Software"), to deal in 5 + // the Software without restriction, including without limitation the rights to 6 + // use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies 7 + // of the Software, and to permit persons to whom the Software is furnished to do 8 + // so, subject to the following conditions : 9 + // The above copyright noticeand this permission notice shall be included in all 10 + // copies or substantial portions of the Software. 11 + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 14 + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 + // SOFTWARE. 18 + 19 + precision mediump float; 20 + #define M_PI 3.1415926536 21 + 22 + float cbrt(float x) { 23 + return sign(x)*pow(abs(x), 1.0 / 3.0); 24 + } 25 + 26 + float srgb_transfer_function(float a) { 27 + return .0031308 >= a ? 12.92 * a : 1.055 * pow(a, .4166666666666667) - .055; 28 + } 29 + 30 + vec3 oklab_to_linear_srgb(vec3 c) { 31 + float l_ = c.x + 0.3963377774 * c.y + 0.2158037573 * c.z; 32 + float m_ = c.x - 0.1055613458 * c.y - 0.0638541728 * c.z; 33 + float s_ = c.x - 0.0894841775 * c.y - 1.2914855480 * c.z; 34 + 35 + float l = l_ * l_ * l_; 36 + float m = m_ * m_ * m_; 37 + float s = s_ * s_ * s_; 38 + 39 + return vec3( 40 + +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, 41 + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, 42 + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s 43 + ); 44 + } 45 + 46 + // Finds the maximum saturation possible for a given hue that fits in sRGB 47 + // Saturation here is defined as S = C/L 48 + // a and b must be normalized so a^2 + b^2 == 1 49 + float compute_max_saturation(float a, float b) { 50 + // Max saturation will be when one of r, g or b goes below zero. 51 + 52 + // Select different coefficients depending on which component goes below zero first 53 + float k0, k1, k2, k3, k4, wl, wm, ws; 54 + 55 + if (-1.88170328 * a - 0.80936493 * b > 1.0) 56 + { 57 + // Red component 58 + k0 = +1.19086277; k1 = +1.76576728; k2 = +0.59662641; k3 = +0.75515197; k4 = +0.56771245; 59 + wl = +4.0767416621; wm = -3.3077115913; ws = +0.2309699292; 60 + } 61 + else if (1.81444104 * a - 1.19445276 * b > 1.0) 62 + { 63 + // Green component 64 + k0 = +0.73956515; k1 = -0.45954404; k2 = +0.08285427; k3 = +0.12541070; k4 = +0.14503204; 65 + wl = -1.2684380046; wm = +2.6097574011; ws = -0.3413193965; 66 + } 67 + else 68 + { 69 + // Blue component 70 + k0 = +1.35733652; k1 = -0.00915799; k2 = -1.15130210; k3 = -0.50559606; k4 = +0.00692167; 71 + wl = -0.0041960863; wm = -0.7034186147; ws = +1.7076147010; 72 + } 73 + 74 + // Approximate max saturation using a polynomial: 75 + float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b; 76 + 77 + // Do one step Halley's method to get closer 78 + // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite 79 + // this should be sufficient for most applications, otherwise do two/three steps 80 + 81 + float k_l = +0.3963377774 * a + 0.2158037573 * b; 82 + float k_m = -0.1055613458 * a - 0.0638541728 * b; 83 + float k_s = -0.0894841775 * a - 1.2914855480 * b; 84 + 85 + { 86 + float l_ = 1.0 + S * k_l; 87 + float m_ = 1.0 + S * k_m; 88 + float s_ = 1.0 + S * k_s; 89 + 90 + float l = l_ * l_ * l_; 91 + float m = m_ * m_ * m_; 92 + float s = s_ * s_ * s_; 93 + 94 + float l_dS = 3.0 * k_l * l_ * l_; 95 + float m_dS = 3.0 * k_m * m_ * m_; 96 + float s_dS = 3.0 * k_s * s_ * s_; 97 + 98 + float l_dS2 = 6.0 * k_l * k_l * l_; 99 + float m_dS2 = 6.0 * k_m * k_m * m_; 100 + float s_dS2 = 6.0 * k_s * k_s * s_; 101 + 102 + float f = wl * l + wm * m + ws * s; 103 + float f1 = wl * l_dS + wm * m_dS + ws * s_dS; 104 + float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2; 105 + 106 + S = S - f * f1 / (f1 * f1 - 0.5 * f * f2); 107 + } 108 + 109 + return S; 110 + } 111 + 112 + // finds L_cusp and C_cusp for a given hue 113 + // a and b must be normalized so a^2 + b^2 == 1 114 + vec2 find_cusp(float a, float b) { 115 + // First, find the maximum saturation (saturation S = C/L) 116 + float S_cusp = compute_max_saturation(a, b); 117 + 118 + // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1: 119 + vec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b )); 120 + float L_cusp = cbrt(1.0 / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b)); 121 + float C_cusp = L_cusp * S_cusp; 122 + 123 + return vec2( L_cusp , C_cusp ); 124 + } 125 + 126 + float toe_inv(float x) { 127 + float k_1 = 0.206; 128 + float k_2 = 0.03; 129 + float k_3 = (1.0 + k_1) / (1.0 + k_2); 130 + return (x * x + k_1 * x) / (k_3 * (x + k_2)); 131 + } 132 + 133 + vec2 to_ST(vec2 cusp) { 134 + float L = cusp.x; 135 + float C = cusp.y; 136 + return vec2( C / L, C / (1.0 - L) ); 137 + } 138 + 139 + vec3 okhsv_to_srgb(vec3 hsv) { 140 + float h = hsv.x; 141 + float s = hsv.y; 142 + float v = hsv.z; 143 + 144 + float a_ = cos(2.0 * M_PI * h); 145 + float b_ = sin(2.0 * M_PI * h); 146 + 147 + vec2 cusp = find_cusp(a_, b_); 148 + vec2 ST_max = to_ST(cusp); 149 + float S_max = ST_max.x; 150 + float T_max = ST_max.y; 151 + float S_0 = 0.5; 152 + float k = 1.0- S_0 / S_max; 153 + 154 + // first we compute L and V as if the gamut is a perfect triangle: 155 + 156 + // L, C when v==1: 157 + float L_v = 1.0 - s * S_0 / (S_0 + T_max - T_max * k * s); 158 + float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s); 159 + 160 + float L = v * L_v; 161 + float C = v * C_v; 162 + 163 + // then we compensate for both toe and the curved top part of the triangle: 164 + float L_vt = toe_inv(L_v); 165 + float C_vt = C_v * L_vt / L_v; 166 + 167 + float L_new = toe_inv(L); 168 + C = C * L_new / L; 169 + L = L_new; 170 + 171 + vec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt )); 172 + float scale_L = cbrt(1.0 / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.0))); 173 + 174 + L = L * scale_L; 175 + C = C * scale_L; 176 + 177 + vec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ )); 178 + return vec3( 179 + srgb_transfer_function(rgb.r), 180 + srgb_transfer_function(rgb.g), 181 + srgb_transfer_function(rgb.b) 182 + ); 183 + }
+2
src/lib/components/extra/color-picker/base/shaders/readme.md
··· 1 + shaders adapted from https://github.com/dokozero/okcolor 2 + the source in this folder is minified and included in a ts file to enable svelte-package to be used
+5
src/lib/components/extra/color-picker/base/shaders/v_shader.glsl
··· 1 + attribute vec4 position; 2 + 3 + void main() { 4 + gl_Position = position; 5 + }