feat(pages): home page base
This commit is contained in:
		
							parent
							
								
									87cfc9c1f3
								
							
						
					
					
						commit
						202896fa95
					
				| @ -2,6 +2,8 @@ | ||||
|   "name": "@it-tools/root", | ||||
|   "version": "0.0.0", | ||||
|   "description": "IT Tools monorepo root", | ||||
|   "packageManager": "pnpm@9.12.2", | ||||
| 
 | ||||
|   "scripts": { | ||||
|     "dev": "pnpm -F @it-tools/app dev" | ||||
|   }, | ||||
| @ -10,6 +12,5 @@ | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/CorentinTh/it-tools" | ||||
|   }, | ||||
|   "packageManager": "pnpm@9.11.0" | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <NuxtRouteAnnouncer /> | ||||
|     <NuxtWelcome /> | ||||
|   </div> | ||||
|   <NuxtLayout> | ||||
|     <NuxtPage /> | ||||
|   </NuxtLayout> | ||||
| </template> | ||||
|  | ||||
							
								
								
									
										77
									
								
								packages/app/assets/css/tailwind.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								packages/app/assets/css/tailwind.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| @tailwind base; | ||||
| @tailwind components; | ||||
| @tailwind utilities; | ||||
|   | ||||
| 
 | ||||
| @layer base { | ||||
|   :root { | ||||
|     --background: 0 0% 100%; | ||||
|     --foreground: 240 10% 3.9%; | ||||
|    | ||||
|     --card: 0 0% 100%; | ||||
|     --card-foreground: 240 10% 3.9%; | ||||
|    | ||||
|     --popover: 0 0% 100%; | ||||
|     --popover-foreground: 240 10% 3.9%; | ||||
|    | ||||
|     --primary: 240 5.9% 10%; | ||||
|     --primary-foreground: 0 0% 98%; | ||||
|    | ||||
|     --secondary: 240 4.8% 95.9%; | ||||
|     --secondary-foreground: 240 5.9% 10%; | ||||
|    | ||||
|     --muted: 240 4.8% 95.9%; | ||||
|     --muted-foreground: 240 3.8% 46.1%; | ||||
|    | ||||
|     --accent: 240 4.8% 95.9%; | ||||
|     --accent-foreground: 240 5.9% 10%; | ||||
|    | ||||
|     --destructive: 0 84.2% 60.2%; | ||||
|     --destructive-foreground: 0 0% 98%; | ||||
|    | ||||
|     --border:240 5.9% 90%; | ||||
|     --input:240 5.9% 90%; | ||||
|     --ring:240 5.9% 10%; | ||||
|     --radius: 0.5rem; | ||||
|   } | ||||
|     | ||||
|   .dark { | ||||
|     --background:0 0% 9%; | ||||
|     --foreground:0 0% 98%; | ||||
|    | ||||
|     --card: 0 0% 7%; | ||||
|     --card-foreground:0 0% 98%; | ||||
|    | ||||
|     --popover:240 10% 3.9%; | ||||
|     --popover-foreground:0 0% 98%; | ||||
|    | ||||
|     --primary: 83 79% 55%; | ||||
|     --primary-foreground:240 5.9% 10%; | ||||
|    | ||||
|     --secondary:240 3.7% 15.9%; | ||||
|     --secondary-foreground:0 0% 98%; | ||||
|    | ||||
|     --muted:240 3.7% 15.9%; | ||||
|     --muted-foreground:240 5% 64.9%; | ||||
|    | ||||
|     --accent:240 3.7% 15.9%; | ||||
|     --accent-foreground:0 0% 98%; | ||||
|    | ||||
|     --destructive:0 62.8% 30.6%; | ||||
|     --destructive-foreground:0 0% 98%; | ||||
|    | ||||
|     --border:240 3.7% 15.9%; | ||||
|     --input:240 3.7% 15.9%; | ||||
|     --ring:240 4.9% 83.9%; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|   | ||||
| @layer base { | ||||
|   * { | ||||
|     @apply border-border; | ||||
|   } | ||||
|   body { | ||||
|     @apply bg-background text-foreground; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								packages/app/components.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/app/components.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| { | ||||
|   "$schema": "https://shadcn-vue.com/schema.json", | ||||
|   "style": "new-york", | ||||
|   "typescript": true, | ||||
|   "tsConfigPath": ".nuxt/tsconfig.json", | ||||
|   "tailwind": { | ||||
|     "config": "tailwind.config.js", | ||||
|     "css": "assets/css/tailwind.css", | ||||
|     "baseColor": "neutral", | ||||
|     "cssVariables": true, | ||||
|     "prefix": "" | ||||
|   }, | ||||
|   "framework": "nuxt", | ||||
|   "aliases": { | ||||
|     "components": "@/src/modules/ui/components", | ||||
|     "ui": "@/src/modules/ui/components", | ||||
|     "utils": "@/src/modules/shared/style/cn" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								packages/app/i18n.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/app/i18n.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| export default defineI18nConfig(() => ({ | ||||
|   legacy: false, | ||||
|   locale: 'en', | ||||
|   messages: { | ||||
|     en: { | ||||
|       welcome: 'Welcome', | ||||
|     }, | ||||
|     fr: { | ||||
|       welcome: 'Bienvenue', | ||||
|     }, | ||||
|   }, | ||||
| })); | ||||
| @ -2,4 +2,40 @@ | ||||
| export default defineNuxtConfig({ | ||||
|   compatibilityDate: '2024-04-03', | ||||
|   devtools: { enabled: true }, | ||||
| 
 | ||||
|   extends: [ | ||||
|     'src/modules/app', | ||||
|   ], | ||||
| 
 | ||||
|   modules: [ | ||||
|     '@nuxtjs/tailwindcss', | ||||
|     'shadcn-nuxt', | ||||
|     '@nuxt/fonts', | ||||
|     '@nuxt/icon', | ||||
|     '@vueuse/nuxt', | ||||
|     '@nuxtjs/color-mode', | ||||
|     '@nuxtjs/i18n', | ||||
|   ], | ||||
| 
 | ||||
|   fonts: { | ||||
|     provider: 'bunny', | ||||
|     defaults: { | ||||
|       weights: [400, 500, 600, 700, 800], | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   colorMode: { | ||||
|     preference: 'system', | ||||
|     fallback: 'dark', | ||||
|     classSuffix: '', | ||||
|     storage: 'cookie', | ||||
|     storageKey: 'itts-color-mode', | ||||
|   }, | ||||
| 
 | ||||
|   i18n: { | ||||
|     strategy: 'prefix', | ||||
|     vueI18n: './i18n.config.ts', | ||||
|     locales: ['en', 'fr'], | ||||
|     defaultLocale: 'en', | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| @ -2,6 +2,8 @@ | ||||
|   "name": "@it-tools/app", | ||||
|   "type": "module", | ||||
|   "private": true, | ||||
|   "packageManager": "pnpm@9.12.2", | ||||
| 
 | ||||
|   "scripts": { | ||||
|     "dev": "nuxt dev", | ||||
|     "build": "nuxt build", | ||||
| @ -17,13 +19,28 @@ | ||||
|     "typecheck": "tsc --noEmit" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@nuxt/fonts": "^0.10.2", | ||||
|     "@nuxt/icon": "^1.5.6", | ||||
|     "@nuxtjs/color-mode": "^3.5.2", | ||||
|     "@nuxtjs/i18n": "^8.5.5", | ||||
|     "class-variance-authority": "^0.7.0", | ||||
|     "clsx": "^2.1.1", | ||||
|     "lucide-vue-next": "^0.453.0", | ||||
|     "nuxt": "^3.13.2", | ||||
|     "radix-vue": "^1.9.7", | ||||
|     "shadcn-nuxt": "^0.10.4", | ||||
|     "tailwind-merge": "^2.5.4", | ||||
|     "tailwindcss-animate": "^1.0.7", | ||||
|     "vue": "latest", | ||||
|     "vue-router": "latest" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@antfu/eslint-config": "^3.8.0", | ||||
|     "@nuxtjs/tailwindcss": "^6.12.2", | ||||
|     "@vueuse/core": "^11.1.0", | ||||
|     "@vueuse/nuxt": "^11.1.0", | ||||
|     "eslint": "^9.13.0", | ||||
|     "typescript": "^5.6.3", | ||||
|     "vitest": "^2.1.3" | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										113
									
								
								packages/app/src/modules/app/components/app-footer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								packages/app/src/modules/app/components/app-footer.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | ||||
| <script setup> | ||||
| const localePath = useLocalePath(); | ||||
| 
 | ||||
| const sections = computed(() => [ | ||||
|   { | ||||
|     title: 'Lorem', | ||||
|     items: [ | ||||
|       { label: 'Foo', to: '/foo' }, | ||||
|       { label: 'Bar', to: '/bar' }, | ||||
|       { label: 'Baz', to: '/baz' }, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     title: 'Ipsum', | ||||
|     items: [ | ||||
|       { label: 'Foo', to: '/foo' }, | ||||
|       { label: 'Bar', to: '/bar' }, | ||||
|       { label: 'Baz', to: '/baz' }, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     title: 'Dolor', | ||||
|     items: [ | ||||
|       { label: 'Foo', to: '/foo' }, | ||||
|       { label: 'Bar', to: '/bar' }, | ||||
|       { label: 'Baz', to: '/baz' }, | ||||
|     ], | ||||
|   }, | ||||
| 
 | ||||
| ]); | ||||
| 
 | ||||
| const socialLinks = [ | ||||
|   { | ||||
|     icon: 'i-tabler-brand-github', | ||||
|     href: 'https://github.com/CorentinTh/it-tools', | ||||
|     label: 'GitHub', | ||||
|   }, | ||||
|   { | ||||
|     icon: 'i-tabler-brand-x', | ||||
|     href: 'https://x.com/ittoolsdottech', | ||||
|     label: 'X', | ||||
|   }, | ||||
|   { | ||||
|     icon: 'i-tabler-coffee', | ||||
|     href: 'https://buymeacoffee.com/cthmsst', | ||||
|     label: 'Support the project', | ||||
|   }, | ||||
| ]; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <footer class="bg-card border-t border-border"> | ||||
|     <div class="py-12 px-6 max-w-screen-xl mx-auto "> | ||||
|       <div class="flex items-start justify-between flex-col md:flex-row gap-12"> | ||||
|         <div> | ||||
|           <div class="flex items-center gap-2"> | ||||
|             <NuxtLink :to="localePath('/')" class="text-2xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250 group text-muted-foreground flex items-center gap-1"> | ||||
|               <span class="font-bold group-hover:text-foreground transition">IT</span> | ||||
|               <span class="text-[80%] font-extrabold border-[2px] leading-none border-current rounded-md px-1 pt-0.5 ml-1 group-hover:text-primary transition">TOOLS</span> | ||||
|             </NuxtLink> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex items-center gap-2 mt-4"> | ||||
|             <!-- {socialLinks.map(({ icon, href, label }) => ( | ||||
|             <a href="{href}" target="_blank" rel="noopener noreferrer" class="text-2xl text-muted-foreground hover:text-primary transition" aria-label="{label}"> | ||||
|               <div class="{icon}" /> | ||||
|             </a> | ||||
|             ))} --> | ||||
|             <a | ||||
|               v-for="socialLink in socialLinks" :key="socialLink.label" :href="socialLink.href" target="_blank" rel="noopener noreferrer" class="text-2xl text-muted-foreground hover:text-primary transition" :aria-label="socialLink.label" | ||||
|             > | ||||
|               <Icon :name="socialLink.icon" /> | ||||
|             </a> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="text-muted-foreground mt-2"> | ||||
|             Crafted on Earth by | ||||
|             <a href="https://corentin.tech" target="_blank" rel="noopener" class="hover:text-primary transition"> | ||||
|               Corentin Thomasset | ||||
|             </a> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="grid grid-cols-1 sm:grid-cols-3 gap-12"> | ||||
|           <div v-for="section in sections" :key="section.title"> | ||||
|             <h4 class="font-semibold text-foreground"> | ||||
|               {{ section.title }} | ||||
|             </h4> | ||||
|             <ul class="mt-4"> | ||||
|               <li v-for="item in section.items" :key="item.label" class="mt-1"> | ||||
|                 <NuxtLink v-if="item.to" :to="localePath(item.to)" class="text-muted-foreground hover:text-primary transition"> | ||||
|                   {{ item.label }} | ||||
|                 </NuxtLink> | ||||
|                 <a v-else :href="item.href" target="_blank" rel="noopener" class="text-muted-foreground hover:text-primary transition"> | ||||
|                   {{ item.label }} | ||||
|                 </a> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-xs text-muted-foreground border-t border-border pt-4 mt-12"> | ||||
|         <span> | ||||
|           © | ||||
|           {{ new Date().getFullYear() }} | ||||
|           Corentin Thomasset | ||||
|         </span> | ||||
|       </div> | ||||
|       <div class="text-xs text-foreground opacity-80%" /> | ||||
|     </div> | ||||
|   </footer> | ||||
| </template> | ||||
							
								
								
									
										41
									
								
								packages/app/src/modules/app/components/app-header.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								packages/app/src/modules/app/components/app-header.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| <script setup> | ||||
| import { Button } from '@/src/modules/ui/components/button'; | ||||
| import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/src/modules/ui/components/dropdown-menu'; | ||||
| 
 | ||||
| const colorMode = useColorMode(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="w-full border-b"> | ||||
|     <div class="max-w-screen-xl mx-auto flex items-center justify-between py-2 px-6"> | ||||
|       <NuxtLink variant="link" class="text-xl font-semibold border-b border-transparent hover:no-underline h-auto px-1 rounded-none !transition-border-color-250" :as="Button" :to="localePath('/')" aria-label="Home"> | ||||
|         <span class="font-bold text-foreground">IT</span> | ||||
|         <span class="text-[80%] font-extrabold border-[2px] leading-none border-current rounded-md px-1 py-0.5 ml-1.5 text-primary">TOOLS</span> | ||||
|       </NuxtLink> | ||||
| 
 | ||||
|       <DropdownMenu> | ||||
|         <DropdownMenuTrigger as-child> | ||||
|           <Button variant="ghost" size="icon"> | ||||
|             <Icon name="i-tabler-moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> | ||||
|             <Icon name="i-tabler-sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> | ||||
|             <span class="sr-only">Toggle theme</span> | ||||
|           </Button> | ||||
|         </DropdownMenuTrigger> | ||||
|         <DropdownMenuContent align="end"> | ||||
|           <DropdownMenuItem class="cursor-pointer" :class="{ 'font-bold': colorMode.preference === 'light' }" @click="colorMode.preference = 'light'"> | ||||
|             <Icon name="i-tabler-sun" class="mr-2 size-4" /> | ||||
|             Light | ||||
|           </DropdownMenuItem> | ||||
|           <DropdownMenuItem class="cursor-pointer" :class="{ 'font-bold': colorMode.preference === 'dark' }" @click="colorMode.preference = 'dark'"> | ||||
|             <Icon name="i-tabler-moon" class="mr-2 size-4" /> | ||||
|             Dark | ||||
|           </DropdownMenuItem> | ||||
|           <DropdownMenuItem class="cursor-pointer" :class="{ 'font-bold': colorMode.preference === 'system' }" @click="colorMode.preference = 'system'"> | ||||
|             <Icon name="i-tabler-device-laptop" class="mr-2 size-4" /> | ||||
|             System | ||||
|           </DropdownMenuItem> | ||||
|         </DropdownMenuContent> | ||||
|       </DropdownMenu> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										11
									
								
								packages/app/src/modules/app/components/grid-background.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/app/src/modules/app/components/grid-background.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| const props = withDefaults(defineProps<{ fadeBottom?: boolean; faderClass?: string }>(), { fadeBottom: true }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="w-full bg-[linear-gradient(to_right,#80808010_1px,transparent_1px),linear-gradient(to_bottom,#80808010_1px,transparent_1px)] bg-[size:48px_48px] pt-20"> | ||||
|     <slot /> | ||||
| 
 | ||||
|     <div v-if="props.fadeBottom" class="bg-gradient-to-t from-background to-transparent h-24 mt-24" :class="props.faderClass" /> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										11
									
								
								packages/app/src/modules/app/layouts/default.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/app/src/modules/app/layouts/default.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| <template> | ||||
|   <div class="w-full min-h-screen text-sm relative font-sans flex flex-col"> | ||||
|     <app-header /> | ||||
| 
 | ||||
|     <div class="flex-1 pb-6"> | ||||
|       <slot /> | ||||
|     </div> | ||||
| 
 | ||||
|     <app-footer /> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										0
									
								
								packages/app/src/modules/app/nuxt.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								packages/app/src/modules/app/nuxt.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										34
									
								
								packages/app/src/modules/app/pages/[...404].vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								packages/app/src/modules/app/pages/[...404].vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| <script setup lang="ts"> | ||||
| import { Button } from '@/src/modules/ui/components/button'; | ||||
| 
 | ||||
| const localePath = useLocalePath(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="flex justify-center text-center"> | ||||
|     <div> | ||||
|       <h1 class="text-3xl font-light text-muted-foreground"> | ||||
|         404 | ||||
|       </h1> | ||||
|       <h2 class="font-semibold text-lg my-2"> | ||||
|         Page not found | ||||
|       </h2> | ||||
| 
 | ||||
|       <p class="text-muted-foreground"> | ||||
|         The page you are looking for does not seem to exist. | ||||
|       </p> | ||||
| 
 | ||||
|       <p class="text-muted-foreground"> | ||||
|         Please check the URL and try again. | ||||
|       </p> | ||||
| 
 | ||||
|       <Button as-child variant="secondary" class="mt-4"> | ||||
|         <NuxtLink :to="localePath('/')"> | ||||
|           <Icon name="i-tabler-arrow-left" class="mr-2 size-4" /> | ||||
| 
 | ||||
|           Go back home | ||||
|         </NuxtLink> | ||||
|       </Button> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										57
									
								
								packages/app/src/modules/app/pages/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								packages/app/src/modules/app/pages/index.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| <script setup> | ||||
| import { Badge } from '@/src/modules/ui/components/badge'; | ||||
| import { Button } from '@/src/modules/ui/components/button'; | ||||
| import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/src/modules/ui/components/dropdown-menu'; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <grid-background> | ||||
|     <div class="flex gap-24 mx-auto justify-center pb-8 mt-8 items-center px-6"> | ||||
|       <div class="max-w-xl"> | ||||
|         <div class="flex gap-2"> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10"> | ||||
|             <!-- {{ $t('landing.hero.badges.open-source') }} --> | ||||
|             Open Source | ||||
|           </Badge> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10"> | ||||
|             <!-- {{ $t('landing.hero.badges.free') }} --> | ||||
|             Free | ||||
|           </Badge> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10"> | ||||
|             <!-- {{ $t('landing.hero.badges.self-hostable') }} --> | ||||
|             Self-hostable | ||||
|           </Badge> | ||||
|         </div> | ||||
| 
 | ||||
|         <h1 class="text-5xl font-semibold border-b border-transparent hover:no-underline h-auto py-0 px-1 ml--1 rounded-none !transition-border-color-250 my-6"> | ||||
|           <span class="font-bold ">IT</span> | ||||
|           <span class="text-[90%] text-primary font-extrabold border-[5px] leading-none border-current rounded-xl px-2 py-0.5 ml-3">TOOLS</span> | ||||
|         </h1> | ||||
| 
 | ||||
|         <p class="text-xl text-gray-400 mb-4"> | ||||
|           <!-- {{ $t('app.description') }} --> | ||||
|           The open-source collection of handy online tools to help developers in their daily life. | ||||
|         </p> | ||||
| 
 | ||||
|         <div class="flex gap-4"> | ||||
|           <Button> | ||||
|             <!-- {{ $t('landing.hero.all-the-tools') }} --> | ||||
|             All the tools | ||||
|             <Icon name="i-tabler-arrow-right" class="ml-2 size-4" /> | ||||
|           </Button> | ||||
| 
 | ||||
|           <Button variant="outline"> | ||||
|             <!-- {{ $t('landing.hero.search-tools') }} --> | ||||
|             <Icon name="i-tabler-search" class="mr-2 size-4" /> | ||||
|             Search tools | ||||
|           </Button> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="relative hidden sm:block"> | ||||
|         <div class="absolute top-4 left-0 w-full h-full flex items-center justify-center blur-2xl rounded-full opacity-20 bg-gradient-to-br from-primary to-transparent" /> | ||||
|         <Icon name="i-tabler-terminal" class="text-9xl text-primary m-8" /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </grid-background> | ||||
| </template> | ||||
							
								
								
									
										4
									
								
								packages/app/src/modules/shared/style/cn.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/app/src/modules/shared/style/cn.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| import { type ClassValue, clsx } from 'clsx'; | ||||
| import { twMerge } from 'tailwind-merge'; | ||||
| 
 | ||||
| export const cn = (...classLists: ClassValue[]) => twMerge(clsx(classLists)); | ||||
							
								
								
									
										16
									
								
								packages/app/src/modules/ui/components/badge/Badge.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/app/src/modules/ui/components/badge/Badge.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { type BadgeVariants, badgeVariants } from '.' | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   variant?: BadgeVariants['variant'] | ||||
|   class?: HTMLAttributes['class'] | ||||
| }>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div :class="cn(badgeVariants({ variant }), props.class)"> | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										25
									
								
								packages/app/src/modules/ui/components/badge/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/app/src/modules/ui/components/badge/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import { cva, type VariantProps } from 'class-variance-authority' | ||||
| 
 | ||||
| export { default as Badge } from './Badge.vue' | ||||
| 
 | ||||
| export const badgeVariants = cva( | ||||
|   'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|         default: | ||||
|           'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', | ||||
|         secondary: | ||||
|           'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', | ||||
|         destructive: | ||||
|           'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', | ||||
|         outline: 'text-foreground', | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       variant: 'default', | ||||
|     }, | ||||
|   }, | ||||
| ) | ||||
| 
 | ||||
| export type BadgeVariants = VariantProps<typeof badgeVariants> | ||||
							
								
								
									
										26
									
								
								packages/app/src/modules/ui/components/button/Button.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/app/src/modules/ui/components/button/Button.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue'; | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { Primitive, type PrimitiveProps } from 'radix-vue'; | ||||
| import { type ButtonVariants, buttonVariants } from '.'; | ||||
| 
 | ||||
| type Props = { | ||||
|   variant?: ButtonVariants['variant']; | ||||
|   size?: ButtonVariants['size']; | ||||
|   class?: HTMLAttributes['class']; | ||||
| } & PrimitiveProps; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<Props>(), { | ||||
|   as: 'button', | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Primitive | ||||
|     :as="as" | ||||
|     :as-child="asChild" | ||||
|     :class="cn(buttonVariants({ variant, size }), props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </Primitive> | ||||
| </template> | ||||
							
								
								
									
										32
									
								
								packages/app/src/modules/ui/components/button/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/app/src/modules/ui/components/button/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| import { cva, type VariantProps } from 'class-variance-authority'; | ||||
| 
 | ||||
| export { default as Button } from './Button.vue'; | ||||
| 
 | ||||
| export const buttonVariants = cva( | ||||
|   'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|         default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', | ||||
|         destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', | ||||
|         outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', | ||||
|         secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', | ||||
|         ghost: 'hover:bg-accent hover:text-accent-foreground', | ||||
|         link: 'text-primary underline-offset-4 hover:underline', | ||||
|       }, | ||||
|       size: { | ||||
|         default: 'h-9 px-4 py-2', | ||||
|         xs: 'h-7 rounded px-2', | ||||
|         sm: 'h-8 rounded-md px-3 text-xs', | ||||
|         lg: 'h-10 rounded-md px-8', | ||||
|         icon: 'h-9 w-9', | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       variant: 'default', | ||||
|       size: 'default', | ||||
|     }, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| export type ButtonVariants = VariantProps<typeof buttonVariants>; | ||||
| @ -0,0 +1,14 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DropdownMenuRoot, type DropdownMenuRootEmits, type DropdownMenuRootProps, useForwardPropsEmits } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuRootProps>() | ||||
| const emits = defineEmits<DropdownMenuRootEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuRoot v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </DropdownMenuRoot> | ||||
| </template> | ||||
| @ -0,0 +1,39 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { | ||||
|   DropdownMenuCheckboxItem, | ||||
|   type DropdownMenuCheckboxItemEmits, | ||||
|   type DropdownMenuCheckboxItemProps, | ||||
|   DropdownMenuItemIndicator, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>() | ||||
| const emits = defineEmits<DropdownMenuCheckboxItemEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuCheckboxItem | ||||
|     v-bind="forwarded" | ||||
|     :class=" cn( | ||||
|       'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | ||||
|       props.class, | ||||
|     )" | ||||
|   > | ||||
|     <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> | ||||
|       <DropdownMenuItemIndicator> | ||||
|          <Icon name="i-tabler-check" class="w-4 h-4" /> | ||||
|       </DropdownMenuItemIndicator> | ||||
|     </span> | ||||
|     <slot /> | ||||
|   </DropdownMenuCheckboxItem> | ||||
| </template> | ||||
| @ -0,0 +1,38 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { | ||||
|   DropdownMenuContent, | ||||
|   type DropdownMenuContentEmits, | ||||
|   type DropdownMenuContentProps, | ||||
|   DropdownMenuPortal, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(), | ||||
|   { | ||||
|     sideOffset: 4, | ||||
|   }, | ||||
| ) | ||||
| const emits = defineEmits<DropdownMenuContentEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuPortal> | ||||
|     <DropdownMenuContent | ||||
|       v-bind="forwarded" | ||||
|       :class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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', props.class)" | ||||
|     > | ||||
|       <slot /> | ||||
|     </DropdownMenuContent> | ||||
|   </DropdownMenuPortal> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuGroupProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuGroup v-bind="props"> | ||||
|     <slot /> | ||||
|   </DropdownMenuGroup> | ||||
| </template> | ||||
| @ -0,0 +1,28 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DropdownMenuItem, type DropdownMenuItemProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes['class'], inset?: boolean }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuItem | ||||
|     v-bind="forwardedProps" | ||||
|     :class="cn( | ||||
|       'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | ||||
|       inset && 'pl-8', | ||||
|       props.class, | ||||
|     )" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DropdownMenuItem> | ||||
| </template> | ||||
| @ -0,0 +1,24 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DropdownMenuLabel, type DropdownMenuLabelProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuLabel | ||||
|     v-bind="forwardedProps" | ||||
|     :class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DropdownMenuLabel> | ||||
| </template> | ||||
| @ -0,0 +1,19 @@ | ||||
| <script setup lang="ts"> | ||||
| import { | ||||
|   DropdownMenuRadioGroup, | ||||
|   type DropdownMenuRadioGroupEmits, | ||||
|   type DropdownMenuRadioGroupProps, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuRadioGroupProps>() | ||||
| const emits = defineEmits<DropdownMenuRadioGroupEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuRadioGroup v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </DropdownMenuRadioGroup> | ||||
| </template> | ||||
| @ -0,0 +1,40 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { | ||||
|   DropdownMenuItemIndicator, | ||||
|   DropdownMenuRadioItem, | ||||
|   type DropdownMenuRadioItemEmits, | ||||
|   type DropdownMenuRadioItemProps, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>(); | ||||
| 
 | ||||
| const emits = defineEmits<DropdownMenuRadioItemEmits>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuRadioItem | ||||
|     v-bind="forwarded" | ||||
|     :class="cn( | ||||
|       'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | ||||
|       props.class, | ||||
|     )" | ||||
|   > | ||||
|     <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> | ||||
|       <DropdownMenuItemIndicator> | ||||
|         <Icon name="i-tabler-point-filled" class="w-4 h-4" /> | ||||
|       </DropdownMenuItemIndicator> | ||||
|     </span> | ||||
|     <slot /> | ||||
|   </DropdownMenuRadioItem> | ||||
| </template> | ||||
| @ -0,0 +1,22 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { | ||||
|   DropdownMenuSeparator, | ||||
|   type DropdownMenuSeparatorProps, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuSeparatorProps & { | ||||
|   class?: HTMLAttributes['class'] | ||||
| }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" /> | ||||
| </template> | ||||
| @ -0,0 +1,14 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   class?: HTMLAttributes['class'] | ||||
| }>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <span :class="cn('ml-auto text-xs tracking-widest opacity-60', props.class)"> | ||||
|     <slot /> | ||||
|   </span> | ||||
| </template> | ||||
| @ -0,0 +1,19 @@ | ||||
| <script setup lang="ts"> | ||||
| import { | ||||
|   DropdownMenuSub, | ||||
|   type DropdownMenuSubEmits, | ||||
|   type DropdownMenuSubProps, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuSubProps>() | ||||
| const emits = defineEmits<DropdownMenuSubEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuSub v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </DropdownMenuSub> | ||||
| </template> | ||||
| @ -0,0 +1,30 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { | ||||
|   DropdownMenuSubContent, | ||||
|   type DropdownMenuSubContentEmits, | ||||
|   type DropdownMenuSubContentProps, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>() | ||||
| const emits = defineEmits<DropdownMenuSubContentEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuSubContent | ||||
|     v-bind="forwarded" | ||||
|     :class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DropdownMenuSubContent> | ||||
| </template> | ||||
| @ -0,0 +1,32 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { | ||||
|   DropdownMenuSubTrigger, | ||||
|   type DropdownMenuSubTriggerProps, | ||||
|   useForwardProps, | ||||
| } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'] }>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuSubTrigger | ||||
|     v-bind="forwardedProps" | ||||
|     :class="cn( | ||||
|       'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent', | ||||
|       props.class, | ||||
|     )" | ||||
|   > | ||||
|     <slot /> | ||||
|     <Icon name="i-tabler-chevron-right" class="ml-auto h-4 w-4" /> | ||||
|   </DropdownMenuSubTrigger> | ||||
| </template> | ||||
| @ -0,0 +1,13 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DropdownMenuTrigger, type DropdownMenuTriggerProps, useForwardProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DropdownMenuTriggerProps>() | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(props) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DropdownMenuTrigger class="outline-none" v-bind="forwardedProps"> | ||||
|     <slot /> | ||||
|   </DropdownMenuTrigger> | ||||
| </template> | ||||
| @ -0,0 +1,16 @@ | ||||
| export { default as DropdownMenu } from './DropdownMenu.vue' | ||||
| 
 | ||||
| export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue' | ||||
| export { default as DropdownMenuContent } from './DropdownMenuContent.vue' | ||||
| export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue' | ||||
| export { default as DropdownMenuItem } from './DropdownMenuItem.vue' | ||||
| export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue' | ||||
| export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue' | ||||
| export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue' | ||||
| export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue' | ||||
| export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue' | ||||
| export { default as DropdownMenuSub } from './DropdownMenuSub.vue' | ||||
| export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue' | ||||
| export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue' | ||||
| export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue' | ||||
| export { DropdownMenuPortal } from 'radix-vue' | ||||
							
								
								
									
										91
									
								
								packages/app/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								packages/app/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| import defaultTheme from 'tailwindcss/defaultTheme'; | ||||
| import animate from 'tailwindcss-animate'; | ||||
| 
 | ||||
| /** @type {import('tailwindcss').Config} */ | ||||
| module.exports = { | ||||
|   content: ['./src/**/*.{vue,js,ts,jsx,tsx}', 'app.vue'], | ||||
|   darkMode: ['class'], | ||||
|   safelist: ['dark'], | ||||
|   prefix: '', | ||||
| 
 | ||||
|   theme: { | ||||
|     container: { | ||||
|       center: true, | ||||
|       padding: '2rem', | ||||
|       screens: { | ||||
|         '2xl': '1400px', | ||||
|       }, | ||||
|     }, | ||||
|     extend: { | ||||
|       fontFamily: { | ||||
|         sans: ['Inter', ...defaultTheme.fontFamily.sans], | ||||
|       }, | ||||
|       colors: { | ||||
|         border: 'hsl(var(--border))', | ||||
|         input: 'hsl(var(--input))', | ||||
|         ring: 'hsl(var(--ring))', | ||||
|         background: 'hsl(var(--background))', | ||||
|         foreground: 'hsl(var(--foreground))', | ||||
|         primary: { | ||||
|           DEFAULT: 'hsl(var(--primary))', | ||||
|           foreground: 'hsl(var(--primary-foreground))', | ||||
|         }, | ||||
|         secondary: { | ||||
|           DEFAULT: 'hsl(var(--secondary))', | ||||
|           foreground: 'hsl(var(--secondary-foreground))', | ||||
|         }, | ||||
|         destructive: { | ||||
|           DEFAULT: 'hsl(var(--destructive))', | ||||
|           foreground: 'hsl(var(--destructive-foreground))', | ||||
|         }, | ||||
|         muted: { | ||||
|           DEFAULT: 'hsl(var(--muted))', | ||||
|           foreground: 'hsl(var(--muted-foreground))', | ||||
|         }, | ||||
|         accent: { | ||||
|           DEFAULT: 'hsl(var(--accent))', | ||||
|           foreground: 'hsl(var(--accent-foreground))', | ||||
|         }, | ||||
|         popover: { | ||||
|           DEFAULT: 'hsl(var(--popover))', | ||||
|           foreground: 'hsl(var(--popover-foreground))', | ||||
|         }, | ||||
|         card: { | ||||
|           DEFAULT: 'hsl(var(--card))', | ||||
|           foreground: 'hsl(var(--card-foreground))', | ||||
|         }, | ||||
|       }, | ||||
|       borderRadius: { | ||||
|         xl: 'calc(var(--radius) + 4px)', | ||||
|         lg: 'var(--radius)', | ||||
|         md: 'calc(var(--radius) - 2px)', | ||||
|         sm: 'calc(var(--radius) - 4px)', | ||||
|       }, | ||||
|       keyframes: { | ||||
|         'accordion-down': { | ||||
|           from: { height: 0 }, | ||||
|           to: { height: 'var(--radix-accordion-content-height)' }, | ||||
|         }, | ||||
|         'accordion-up': { | ||||
|           from: { height: 'var(--radix-accordion-content-height)' }, | ||||
|           to: { height: 0 }, | ||||
|         }, | ||||
|         'collapsible-down': { | ||||
|           from: { height: 0 }, | ||||
|           to: { height: 'var(--radix-collapsible-content-height)' }, | ||||
|         }, | ||||
|         'collapsible-up': { | ||||
|           from: { height: 'var(--radix-collapsible-content-height)' }, | ||||
|           to: { height: 0 }, | ||||
|         }, | ||||
|       }, | ||||
|       animation: { | ||||
|         'accordion-down': 'accordion-down 0.2s ease-out', | ||||
|         'accordion-up': 'accordion-up 0.2s ease-out', | ||||
|         'collapsible-down': 'collapsible-down 0.2s ease-in-out', | ||||
|         'collapsible-up': 'collapsible-up 0.2s ease-in-out', | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   plugins: [animate], | ||||
| }; | ||||
							
								
								
									
										2908
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2908
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user