feat(tools): added token generator
- Added Slider component to the UI library - Added Checkbox component to the UI library - Added Textarea component to the UI library
This commit is contained in:
		
							parent
							
								
									b22173681c
								
							
						
					
					
						commit
						b88f13a7ca
					
				| @ -1,5 +1,10 @@ | ||||
| <script setup lang="ts"> | ||||
| import CommandPalette from './src/modules/command-palette/components/command-palette.vue'; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <NuxtLayout> | ||||
|     <CommandPalette /> | ||||
|     <NuxtPage /> | ||||
|   </NuxtLayout> | ||||
| </template> | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     --popover: 0 0% 100%; | ||||
|     --popover-foreground: 240 10% 3.9%; | ||||
|    | ||||
|     --primary: 150 76% 38%; | ||||
|     --primary: 149 79% 35%; | ||||
|     --primary-foreground: 0 0% 98%; | ||||
|    | ||||
|     --secondary: 240 4.8% 95.9%; | ||||
| @ -36,7 +36,7 @@ | ||||
|   } | ||||
|     | ||||
|   .dark { | ||||
|     --background:240 5% 6%; | ||||
|     --background:240 4% 10%; | ||||
|     --foreground:0 0% 98%; | ||||
|    | ||||
|     --card: 240 5% 8%; | ||||
|  | ||||
| @ -53,4 +53,8 @@ export default defineNuxtConfig({ | ||||
|       { code: 'fr', file: 'fr.yaml', name: 'Français' }, | ||||
|     ], | ||||
|   }, | ||||
| 
 | ||||
|   experimental: { | ||||
|     scanPageMeta: false, // Causes some issues with layouts and hook-registered pages
 | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| @ -29,3 +29,13 @@ tools: | ||||
|     description: >- | ||||
|       Generate random string with the characters you want, uppercase, lowercase | ||||
|       letters, numbers and/or symbols. | ||||
|     placeholder: Generated token will appear here, please select at least one option. | ||||
|     use-uppercase: Include uppercase letters | ||||
|     use-lowercase: Include lowercase letters | ||||
|     use-numbers: Include numbers | ||||
|     use-symbols: Include symbols | ||||
|     length: Length | ||||
|     refresh: Refresh | ||||
|     quantity: Quantity | ||||
|     format: Format | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										28
									
								
								packages/app/src/modules/app/components/sidenav-menu.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								packages/app/src/modules/app/components/sidenav-menu.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <script setup> | ||||
| import { Button } from '@/src/modules/ui/components/button'; | ||||
| import { useToolsStore } from '../../tools/tools.store'; | ||||
| 
 | ||||
| const { tools } = useToolsStore(); | ||||
| const localePath = useLocalePath(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="border-b h-[60px] flex items-center justify-between 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> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="pt-4 px-3 flex flex-col gap-0.5"> | ||||
|     <NuxtLink to="/" class="py-1.5 px-3 flex items-center text-muted-foreground hover:text-foreground transition hover:bg-muted rounded-lg"> | ||||
|       <Icon name="i-tabler-home" class="mr-2 size-4" /> | ||||
|       Home | ||||
|     </NuxtLink> | ||||
| 
 | ||||
|     <NuxtLink v-for="tool in tools" :key="tool.key" class="py-1.5 px-3 flex items-center text-muted-foreground hover:text-foreground transition hover:bg-muted rounded-lg" :to="tool.path" exact-active-class="bg-secondary !text-foreground"> | ||||
|       <Icon :name="tool.icon" class="mr-2 size-4" /> | ||||
|       {{ tool.title }} | ||||
|     </NuxtLink> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										73
									
								
								packages/app/src/modules/app/layouts/sidenav.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								packages/app/src/modules/app/layouts/sidenav.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| <script setup> | ||||
| import { Button } from '@/src/modules/ui/components/button'; | ||||
| import { useCommandPaletteStore } from '../../command-palette/command-palette.store'; | ||||
| import { DropdownMenu } from '../../ui/components/dropdown-menu'; | ||||
| import DropdownMenuContent from '../../ui/components/dropdown-menu/DropdownMenuContent.vue'; | ||||
| import DropdownMenuItem from '../../ui/components/dropdown-menu/DropdownMenuItem.vue'; | ||||
| import DropdownMenuTrigger from '../../ui/components/dropdown-menu/DropdownMenuTrigger.vue'; | ||||
| import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '../../ui/components/sheet'; | ||||
| 
 | ||||
| const { openCommandPalette } = useCommandPaletteStore(); | ||||
| const colorMode = useColorMode(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="w-full min-h-screen text-sm relative font-sans flex flex-row"> | ||||
|     <div class="w-64 border-r bg-white dark:bg-background shrink-0 hidden sm:block"> | ||||
|       <sidenav-menu /> | ||||
|     </div> | ||||
|     <div class="flex-1 flex flex-col"> | ||||
|       <div class="border-b h-[60px] flex items-center justify-between px-6 bg-white dark:bg-background"> | ||||
|         <div class="flex items-center gap-4"> | ||||
|           <div class="sm:hidden"> | ||||
|             <Sheet> | ||||
|               <SheetTrigger> | ||||
|                 <Button variant="ghost" size="icon"> | ||||
|                   <Icon name="i-tabler-menu-2" class="size-5" /> | ||||
|                 </Button> | ||||
|               </SheetTrigger> | ||||
|               <SheetContent side="left" class="p-0 text-sm"> | ||||
|                 <sidenav-menu /> | ||||
|               </SheetContent> | ||||
|             </Sheet> | ||||
|           </div> | ||||
| 
 | ||||
|           <Button variant="outline" class="sm:pr-12 md:pr-24 text-muted-foreground" @click="openCommandPalette"> | ||||
|             <Icon name="i-tabler-search" class="mr-2 size-4" /> | ||||
|             {{ $t('home.search-tools') }} | ||||
|           </Button> | ||||
|         </div> | ||||
| 
 | ||||
|         <div> | ||||
|           <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> | ||||
| 
 | ||||
|       <div class="flex-1"> | ||||
|         <slot /> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| @ -1,12 +1,14 @@ | ||||
| <script setup> | ||||
| import { Badge } from '@/src/modules/ui/components/badge'; | ||||
| import { Button, buttonVariants } from '@/src/modules/ui/components/button'; | ||||
| import { useCommandPaletteStore } from '../../command-palette/command-palette.store'; | ||||
| import { cn } from '../../shared/style/cn'; | ||||
| import { useToolsStore } from '../../tools/tools.store'; | ||||
| import { CardContent } from '../../ui/components/card'; | ||||
| import Card from '../../ui/components/card/Card.vue'; | ||||
| 
 | ||||
| const { tools } = useToolsStore(); | ||||
| const { openCommandPalette } = useCommandPaletteStore(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
| @ -14,13 +16,13 @@ const { tools } = useToolsStore(); | ||||
|     <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"> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10 shadow-none"> | ||||
|             {{ $t('home.open-source') }} | ||||
|           </Badge> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10"> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10 shadow-none"> | ||||
|             {{ $t('home.free') }} | ||||
|           </Badge> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10"> | ||||
|           <Badge class="text-primary bg-primary/10 hover:bg-primary/10 shadow-none"> | ||||
|             {{ $t('home.self-hostable') }} | ||||
|           </Badge> | ||||
|         </div> | ||||
| @ -40,7 +42,7 @@ const { tools } = useToolsStore(); | ||||
|             <Icon name="i-tabler-arrow-right" class="ml-2 size-4" /> | ||||
|           </Button> | ||||
| 
 | ||||
|           <Button variant="outline"> | ||||
|           <Button variant="outline" @click="openCommandPalette"> | ||||
|             <Icon name="i-tabler-search" class="mr-2 size-4" /> | ||||
|             {{ $t('home.search-tools') }} | ||||
|           </Button> | ||||
|  | ||||
| @ -0,0 +1,16 @@ | ||||
| export const useCommandPaletteStore = defineStore('command-palette', () => { | ||||
|   const isCommandPaletteOpen = ref(false); | ||||
| 
 | ||||
|   return { | ||||
|     isCommandPaletteOpen, | ||||
|     toggleCommandPalette() { | ||||
|       isCommandPaletteOpen.value = !isCommandPaletteOpen.value; | ||||
|     }, | ||||
|     closeCommandPalette() { | ||||
|       isCommandPaletteOpen.value = false; | ||||
|     }, | ||||
|     openCommandPalette() { | ||||
|       isCommandPaletteOpen.value = true; | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
| @ -0,0 +1,78 @@ | ||||
| <script setup lang="ts"> | ||||
| import { useMagicKeys } from '@vueuse/core'; | ||||
| import { useToolsStore } from '../../tools/tools.store'; | ||||
| import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '../../ui/components/command'; | ||||
| import { useCommandPaletteStore } from '../command-palette.store'; | ||||
| 
 | ||||
| const commandPaletteStore = useCommandPaletteStore(); | ||||
| const { tools } = useToolsStore(); | ||||
| 
 | ||||
| onKeyStroke('k', (e) => { | ||||
|   e.preventDefault(); | ||||
| 
 | ||||
|   if (!e.ctrlKey && !e.metaKey) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   commandPaletteStore.toggleCommandPalette(); | ||||
| }); | ||||
| 
 | ||||
| const commandSections = computed(() => [ | ||||
|   { | ||||
|     title: 'Tools', | ||||
|     items: [ | ||||
|       ...tools.map(tool => ({ | ||||
|         label: tool.title, | ||||
|         icon: tool.icon, | ||||
|         action: () => navigateTo(tool.path), | ||||
|       })), | ||||
|     ], | ||||
|   }, | ||||
| ]); | ||||
| 
 | ||||
| function handleSelectCommand({ item }: { item: { label: string; action: () => void; keepOpen?: boolean } }) { | ||||
|   item.action(); | ||||
| 
 | ||||
|   if (!item.keepOpen) { | ||||
|     commandPaletteStore.closeCommandPalette(); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <CommandDialog v-model:open="commandPaletteStore.isCommandPaletteOpen"> | ||||
|     <CommandInput placeholder="Type a command or search..." /> | ||||
|     <CommandList> | ||||
|       <CommandEmpty>{{ $t('command-palette.no-result') }}</CommandEmpty> | ||||
|       <!-- <CommandGroup heading="Suggestions"> | ||||
|         <CommandItem value="calendar"> | ||||
|           Calendar | ||||
|         </CommandItem> | ||||
|         <CommandItem value="search-emoji"> | ||||
|           Search Emoji | ||||
|         </CommandItem> | ||||
|         <CommandItem value="calculator"> | ||||
|           Calculator | ||||
|         </CommandItem> | ||||
|       </CommandGroup> | ||||
|       <CommandSeparator /> | ||||
|       <CommandGroup heading="Settings"> | ||||
|         <CommandItem value="profile"> | ||||
|           Profile | ||||
|         </CommandItem> | ||||
|         <CommandItem value="billing"> | ||||
|           Billing | ||||
|         </CommandItem> | ||||
|         <CommandItem value="settings"> | ||||
|           Settings | ||||
|         </CommandItem> | ||||
|       </CommandGroup> --> | ||||
|       <CommandGroup v-for="section in commandSections" :key="section.title" :heading="section.title"> | ||||
|         <CommandItem v-for="item in section.items" :key="item.label" :value="item.label" @select="handleSelectCommand({ item })"> | ||||
|           <Icon :name="item.icon" class="mr-2 size-4" /> | ||||
|           {{ item.label }} | ||||
|         </CommandItem> | ||||
|       </CommandGroup> | ||||
|     </CommandList> | ||||
|   </CommandDialog> | ||||
| </template> | ||||
| @ -1,36 +1,182 @@ | ||||
| <script setup lang="ts"> | ||||
| import { times } from 'lodash-es'; | ||||
| import { useRefreshableState } from '~/src/modules/shared/composables/useRefreshableState'; | ||||
| import { Button } from '~/src/modules/ui/components/button'; | ||||
| import Card from '~/src/modules/ui/components/card/Card.vue'; | ||||
| import { Checkbox } from '~/src/modules/ui/components/checkbox'; | ||||
| import NumberField from '~/src/modules/ui/components/number-field/NumberField.vue'; | ||||
| import NumberFieldContent from '~/src/modules/ui/components/number-field/NumberFieldContent.vue'; | ||||
| import NumberFieldDecrement from '~/src/modules/ui/components/number-field/NumberFieldDecrement.vue'; | ||||
| import NumberFieldIncrement from '~/src/modules/ui/components/number-field/NumberFieldIncrement.vue'; | ||||
| import NumberFieldInput from '~/src/modules/ui/components/number-field/NumberFieldInput.vue'; | ||||
| import Slider from '~/src/modules/ui/components/slider/Slider.vue'; | ||||
| import { Textarea } from '~/src/modules/ui/components/textarea'; | ||||
| import ToggleGroup from '~/src/modules/ui/components/toggle-group/ToggleGroup.vue'; | ||||
| import ToggleGroupItem from '~/src/modules/ui/components/toggle-group/ToggleGroupItem.vue'; | ||||
| import { createToken } from './token-generator.models'; | ||||
| 
 | ||||
| definePageMeta({ | ||||
|   layout: 'sidenav', | ||||
| }); | ||||
| 
 | ||||
| const withUppercase = ref(true); | ||||
| const withLowercase = ref(true); | ||||
| const withNumbers = ref(true); | ||||
| const withSymbols = ref(false); | ||||
| const length = ref(64); | ||||
| const length = ref(48); | ||||
| 
 | ||||
| const [token, refreshToken] = useRefreshableState( | ||||
|   'token-generator:token', | ||||
|   () => createToken({ | ||||
| const formats = { | ||||
|   raw: { | ||||
|     label: 'Raw', | ||||
|     format: ({ tokens }: { tokens: string[] }) => tokens.join('\n'), | ||||
|   }, | ||||
|   JSON: { | ||||
|     label: 'JSON', | ||||
|     format: ({ tokens }: { tokens: string[] }) => JSON.stringify(tokens), | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const format = ref<keyof typeof formats>('raw'); | ||||
| const quantity = ref(1); | ||||
| 
 | ||||
| function generateToken() { | ||||
|   return createToken({ | ||||
|     withUppercase: withUppercase.value, | ||||
|     withLowercase: withLowercase.value, | ||||
|     withNumbers: withNumbers.value, | ||||
|     withSymbols: withSymbols.value, | ||||
|     length: length.value, | ||||
|   }), | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| const [token, refreshToken] = useRefreshableState( | ||||
|   'token-generator:token', | ||||
|   () => { | ||||
|     const tokens = times( | ||||
|       quantity.value, | ||||
|       generateToken, | ||||
|     ); | ||||
| 
 | ||||
| watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken); | ||||
|     return formats[format.value].format({ tokens }); | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| watch([ | ||||
|   withUppercase, | ||||
|   withLowercase, | ||||
|   withNumbers, | ||||
|   withSymbols, | ||||
|   length, | ||||
|   format, | ||||
|   quantity, | ||||
| ], refreshToken); | ||||
| 
 | ||||
| // const { copy: copyToken } = useCopy({ source: token, notificationText: 'Token copied to clipboard' }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="max-w-screen-md mx-auto p-6"> | ||||
|     <div>{{ token }}</div> | ||||
|   <div class="flex flex-col h-full"> | ||||
|     <div class="p-6 bg-white dark:bg-background border-b"> | ||||
|       <h1 class="text-2xl"> | ||||
|         {{ $t('tools.token-generator.title') }} | ||||
|       </h1> | ||||
|       <p class="text-muted-foreground"> | ||||
|         {{ $t('tools.token-generator.description') }} | ||||
|       </p> | ||||
|     </div> | ||||
| 
 | ||||
|     <Button class="mt-4" @click="refreshToken"> | ||||
|       Generate new token | ||||
|     <div class="h-full flex-1 p-6"> | ||||
|       <Card class="max-w-[550px] mx-auto p-6 bg-white dark:bg-background shadow-none"> | ||||
|         <div class="grid grid-cols-2 gap-4"> | ||||
|           <div class="flex gap-2 items-center"> | ||||
|             <Checkbox id="use-uppercase" v-model:checked="withUppercase" /> | ||||
|             <label for="use-uppercase"> | ||||
|               {{ $t('tools.token-generator.use-uppercase') }} | ||||
|               <span class="text-muted-foreground"> | ||||
|                 (A-Z) | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex gap-2 items-center"> | ||||
|             <Checkbox id="use-lowercase" v-model:checked="withLowercase" /> | ||||
|             <label for="use-lowercase"> | ||||
|               {{ $t('tools.token-generator.use-lowercase') }} | ||||
|               <span class="text-muted-foreground"> | ||||
|                 (a-z) | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex gap-2 items-center"> | ||||
|             <Checkbox id="use-numbers" v-model:checked="withNumbers" /> | ||||
|             <label for="use-numbers"> | ||||
|               {{ $t('tools.token-generator.use-numbers') }} | ||||
|               <span class="text-muted-foreground"> | ||||
|                 (0-9) | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex gap-2 items-center"> | ||||
|             <Checkbox id="use-symbols" v-model:checked="withSymbols" /> | ||||
|             <label for="use-symbols"> | ||||
|               {{ $t('tools.token-generator.use-symbols') }} | ||||
|               <span class="text-muted-foreground"> | ||||
|                 (!@,]*...) | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="flex gap-4 items-center"> | ||||
|             <label for="length" class="shrink-0">{{ $t('tools.token-generator.length') }}</label> | ||||
|             <NumberField id="length" v-model="length" :min="1" :max="1024"> | ||||
|               <NumberFieldContent class="flex-1"> | ||||
|                 <NumberFieldDecrement /> | ||||
|                 <NumberFieldInput /> | ||||
|                 <NumberFieldIncrement /> | ||||
|               </NumberFieldContent> | ||||
|             </NumberField> | ||||
|           </div> | ||||
| 
 | ||||
|           <Slider | ||||
|             :model-value="[length]" | ||||
|             :max="512" | ||||
|             :min="1" | ||||
|             :step="1" | ||||
|             @update:model-value="(v) => length = v?.[0] ?? 1" | ||||
|           /> | ||||
|         </div> | ||||
| 
 | ||||
|         <hr class="my-6"> | ||||
| 
 | ||||
|         <div class="mb-4 flex items-center gap-4"> | ||||
|           <div>{{ $t('tools.token-generator.format') }}</div> | ||||
|           <ToggleGroup v-model="format" type="single" variant="outline"> | ||||
|             <ToggleGroupItem v-for="({ label }, key) in formats" :key="key" :value="key"> | ||||
|               {{ label }} | ||||
|             </ToggleGroupItem> | ||||
|           </ToggleGroup> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="mb-4 flex items-center gap-4"> | ||||
|           <div>{{ $t('tools.token-generator.quantity') }}</div> | ||||
| 
 | ||||
|           <NumberField v-model="quantity" :min="1" :max="100"> | ||||
|             <NumberFieldContent class="flex-1"> | ||||
|               <NumberFieldDecrement /> | ||||
|               <NumberFieldInput /> | ||||
|               <NumberFieldIncrement /> | ||||
|             </NumberFieldContent> | ||||
|           </NumberField> | ||||
|         </div> | ||||
| 
 | ||||
|         <Textarea v-model="token" rows="5" class="font-mono" readonly :placeholder="$t('tools.token-generator.placeholder')" /> | ||||
| 
 | ||||
|         <Button class="mt-4" variant="secondary" @click="refreshToken"> | ||||
|           {{ $t('tools.token-generator.refresh') }} | ||||
|         </Button> | ||||
|       </Card> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @ -7,10 +7,10 @@ export const buttonVariants = cva( | ||||
|   { | ||||
|     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', | ||||
|         default: 'bg-primary text-primary-foreground hover:bg-primary/90', | ||||
|         destructive: 'bg-destructive text-destructive-foreground  hover:bg-destructive/90', | ||||
|         outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', | ||||
|         secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | ||||
|         ghost: 'hover:bg-accent hover:text-accent-foreground', | ||||
|         link: 'text-primary underline-offset-4 hover:underline', | ||||
|       }, | ||||
|  | ||||
| @ -11,7 +11,7 @@ const props = defineProps<{ | ||||
|   <div | ||||
|     :class=" | ||||
|       cn( | ||||
|         'rounded-xl border bg-card text-card-foreground shadow', | ||||
|         'rounded-xl border bg-card text-card-foreground', | ||||
|         props.class, | ||||
|       ) | ||||
|     " | ||||
|  | ||||
							
								
								
									
										32
									
								
								packages/app/src/modules/ui/components/checkbox/Checkbox.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/app/src/modules/ui/components/checkbox/Checkbox.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'; | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>(); | ||||
| const emits = defineEmits<CheckboxRootEmits>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <CheckboxRoot | ||||
|     v-bind="forwarded" | ||||
|     :class=" | ||||
|       cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground', | ||||
|          props.class)" | ||||
|   > | ||||
|     <CheckboxIndicator class="flex h-full w-full items-center justify-center text-current"> | ||||
|       <slot> | ||||
|         <Icon name="i-tabler-check" class="h-4 w-4" /> | ||||
|       </slot> | ||||
|     </CheckboxIndicator> | ||||
|   </CheckboxRoot> | ||||
| </template> | ||||
							
								
								
									
										1
									
								
								packages/app/src/modules/ui/components/checkbox/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/app/src/modules/ui/components/checkbox/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| export { default as Checkbox } from './Checkbox.vue' | ||||
							
								
								
									
										30
									
								
								packages/app/src/modules/ui/components/command/Command.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/app/src/modules/ui/components/command/Command.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxRootEmits, ComboboxRootProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxRoot, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), { | ||||
|   open: true, | ||||
|   modelValue: '', | ||||
| }) | ||||
| 
 | ||||
| const emits = defineEmits<ComboboxRootEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxRoot | ||||
|     v-bind="forwarded" | ||||
|     :class="cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </ComboboxRoot> | ||||
| </template> | ||||
| @ -0,0 +1,21 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { DialogRootEmits, DialogRootProps } from 'radix-vue' | ||||
| import { Dialog, DialogContent } from '@/src/modules/ui/components/dialog' | ||||
| import { useForwardPropsEmits } from 'radix-vue' | ||||
| import Command from './Command.vue' | ||||
| 
 | ||||
| const props = defineProps<DialogRootProps>() | ||||
| const emits = defineEmits<DialogRootEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Dialog v-bind="forwarded"> | ||||
|     <DialogContent class="overflow-hidden p-0 shadow-lg"> | ||||
|       <Command class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> | ||||
|         <slot /> | ||||
|       </Command> | ||||
|     </DialogContent> | ||||
|   </Dialog> | ||||
| </template> | ||||
| @ -0,0 +1,20 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxEmptyProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxEmpty } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxEmpty v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)"> | ||||
|     <slot /> | ||||
|   </ComboboxEmpty> | ||||
| </template> | ||||
| @ -0,0 +1,29 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxGroupProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxGroup, ComboboxLabel } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<ComboboxGroupProps & { | ||||
|   class?: HTMLAttributes['class'] | ||||
|   heading?: string | ||||
| }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxGroup | ||||
|     v-bind="delegatedProps" | ||||
|     :class="cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)" | ||||
|   > | ||||
|     <ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground"> | ||||
|       {{ heading }} | ||||
|     </ComboboxLabel> | ||||
|     <slot /> | ||||
|   </ComboboxGroup> | ||||
| </template> | ||||
| @ -0,0 +1,32 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| defineOptions({ | ||||
|   inheritAttrs: false, | ||||
| }); | ||||
| 
 | ||||
| const props = defineProps<ComboboxInputProps & { | ||||
|   class?: HTMLAttributes['class']; | ||||
| }>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="flex items-center border-b px-3" cmdk-input-wrapper> | ||||
|     <Icon name="i-tabler-search" class="mr-2 h-4 w-4 shrink-0 opacity-50" /> | ||||
|     <ComboboxInput | ||||
|       v-bind="{ ...forwardedProps, ...$attrs }" | ||||
|       auto-focus | ||||
|       :class="cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| @ -0,0 +1,26 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxItemEmits, ComboboxItemProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxItem, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>() | ||||
| const emits = defineEmits<ComboboxItemEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxItem | ||||
|     v-bind="forwarded" | ||||
|     :class="cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </ComboboxItem> | ||||
| </template> | ||||
| @ -0,0 +1,27 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxContentEmits, ComboboxContentProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxContent, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), { | ||||
|   dismissable: false, | ||||
| }) | ||||
| const emits = defineEmits<ComboboxContentEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)"> | ||||
|     <div role="presentation"> | ||||
|       <slot /> | ||||
|     </div> | ||||
|   </ComboboxContent> | ||||
| </template> | ||||
| @ -0,0 +1,23 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { ComboboxSeparatorProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ComboboxSeparator } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ComboboxSeparator | ||||
|     v-bind="delegatedProps" | ||||
|     :class="cn('-mx-1 h-px bg-border', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </ComboboxSeparator> | ||||
| </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 text-muted-foreground', props.class)"> | ||||
|     <slot /> | ||||
|   </span> | ||||
| </template> | ||||
							
								
								
									
										9
									
								
								packages/app/src/modules/ui/components/command/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/app/src/modules/ui/components/command/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| export { default as Command } from './Command.vue' | ||||
| export { default as CommandDialog } from './CommandDialog.vue' | ||||
| export { default as CommandEmpty } from './CommandEmpty.vue' | ||||
| export { default as CommandGroup } from './CommandGroup.vue' | ||||
| export { default as CommandInput } from './CommandInput.vue' | ||||
| export { default as CommandItem } from './CommandItem.vue' | ||||
| export { default as CommandList } from './CommandList.vue' | ||||
| export { default as CommandSeparator } from './CommandSeparator.vue' | ||||
| export { default as CommandShortcut } from './CommandShortcut.vue' | ||||
							
								
								
									
										14
									
								
								packages/app/src/modules/ui/components/dialog/Dialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/app/src/modules/ui/components/dialog/Dialog.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogRootProps>() | ||||
| const emits = defineEmits<DialogRootEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogRoot v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </DialogRoot> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogClose, type DialogCloseProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogCloseProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogClose v-bind="props"> | ||||
|     <slot /> | ||||
|   </DialogClose> | ||||
| </template> | ||||
| @ -0,0 +1,49 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { | ||||
|   DialogClose, | ||||
|   DialogContent, | ||||
|   type DialogContentEmits, | ||||
|   type DialogContentProps, | ||||
|   DialogOverlay, | ||||
|   DialogPortal, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>(); | ||||
| const emits = defineEmits<DialogContentEmits>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogPortal> | ||||
|     <DialogOverlay | ||||
|       class="fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" | ||||
|     /> | ||||
|     <DialogContent | ||||
|       v-bind="forwarded" | ||||
|       :class=" | ||||
|         cn( | ||||
|           'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', | ||||
|           props.class, | ||||
|         )" | ||||
|     > | ||||
|       <slot /> | ||||
| 
 | ||||
|       <DialogClose | ||||
|         class="absolute right-3 top-2 p-1 leading-none rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground" | ||||
|       > | ||||
|         <Icon name="i-tabler-x" class="size-4" /> | ||||
|         <span class="sr-only">Close</span> | ||||
|       </DialogClose> | ||||
|     </DialogContent> | ||||
|   </DialogPortal> | ||||
| </template> | ||||
| @ -0,0 +1,24 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DialogDescription, type DialogDescriptionProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogDescription | ||||
|     v-bind="forwardedProps" | ||||
|     :class="cn('text-sm text-muted-foreground', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DialogDescription> | ||||
| </template> | ||||
| @ -0,0 +1,19 @@ | ||||
| <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> | ||||
|   <div | ||||
|     :class=" | ||||
|       cn( | ||||
|         'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2', | ||||
|         props.class, | ||||
|       ) | ||||
|     " | ||||
|   > | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
| @ -0,0 +1,16 @@ | ||||
| <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> | ||||
|   <div | ||||
|     :class="cn('flex flex-col gap-y-1.5 text-center sm:text-left', props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
| @ -0,0 +1,58 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { | ||||
|   DialogClose, | ||||
|   DialogContent, | ||||
|   type DialogContentEmits, | ||||
|   type DialogContentProps, | ||||
|   DialogOverlay, | ||||
|   DialogPortal, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>(); | ||||
| const emits = defineEmits<DialogContentEmits>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogPortal> | ||||
|     <DialogOverlay | ||||
|       class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" | ||||
|     > | ||||
|       <DialogContent | ||||
|         :class=" | ||||
|           cn( | ||||
|             'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full', | ||||
|             props.class, | ||||
|           ) | ||||
|         " | ||||
|         v-bind="forwarded" | ||||
|         @pointer-down-outside="(event) => { | ||||
|           const originalEvent = event.detail.originalEvent; | ||||
|           const target = originalEvent.target as HTMLElement; | ||||
|           if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) { | ||||
|             event.preventDefault(); | ||||
|           } | ||||
|         }" | ||||
|       > | ||||
|         <slot /> | ||||
| 
 | ||||
|         <DialogClose | ||||
|           class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary" | ||||
|         > | ||||
|           <Icon name="i-tabler-x" class="w-4 h-4" /> | ||||
|           <span class="sr-only">Close</span> | ||||
|         </DialogClose> | ||||
|       </DialogContent> | ||||
|     </DialogOverlay> | ||||
|   </DialogPortal> | ||||
| </template> | ||||
| @ -0,0 +1,29 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DialogTitle, type DialogTitleProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogTitle | ||||
|     v-bind="forwardedProps" | ||||
|     :class=" | ||||
|       cn( | ||||
|         'text-lg font-semibold leading-none tracking-tight', | ||||
|         props.class, | ||||
|       ) | ||||
|     " | ||||
|   > | ||||
|     <slot /> | ||||
|   </DialogTitle> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogTrigger, type DialogTriggerProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogTriggerProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogTrigger v-bind="props"> | ||||
|     <slot /> | ||||
|   </DialogTrigger> | ||||
| </template> | ||||
							
								
								
									
										9
									
								
								packages/app/src/modules/ui/components/dialog/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/app/src/modules/ui/components/dialog/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| export { default as Dialog } from './Dialog.vue' | ||||
| export { default as DialogClose } from './DialogClose.vue' | ||||
| export { default as DialogContent } from './DialogContent.vue' | ||||
| export { default as DialogDescription } from './DialogDescription.vue' | ||||
| export { default as DialogFooter } from './DialogFooter.vue' | ||||
| export { default as DialogHeader } from './DialogHeader.vue' | ||||
| export { default as DialogScrollContent } from './DialogScrollContent.vue' | ||||
| export { default as DialogTitle } from './DialogTitle.vue' | ||||
| export { default as DialogTrigger } from './DialogTrigger.vue' | ||||
| @ -0,0 +1,23 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { NumberFieldRootEmits, NumberFieldRootProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { NumberFieldRoot, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<NumberFieldRootProps & { class?: HTMLAttributes['class'] }>() | ||||
| const emits = defineEmits<NumberFieldRootEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <NumberFieldRoot v-bind="forwarded" :class="cn('grid gap-1.5', props.class)"> | ||||
|     <slot /> | ||||
|   </NumberFieldRoot> | ||||
| </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> | ||||
|   <div :class="cn('relative [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5 [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5', props.class)"> | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
| @ -0,0 +1,25 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { NumberFieldDecrementProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { Minus } from 'lucide-vue-next' | ||||
| import { NumberFieldDecrement, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<NumberFieldDecrementProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <NumberFieldDecrement data-slot="decrement" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)"> | ||||
|     <slot> | ||||
|       <Minus class="h-4 w-4" /> | ||||
|     </slot> | ||||
|   </NumberFieldDecrement> | ||||
| </template> | ||||
| @ -0,0 +1,25 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { NumberFieldIncrementProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { Plus } from 'lucide-vue-next' | ||||
| import { NumberFieldIncrement, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<NumberFieldIncrementProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <NumberFieldIncrement data-slot="increment" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)"> | ||||
|     <slot> | ||||
|       <Plus class="h-4 w-4" /> | ||||
|     </slot> | ||||
|   </NumberFieldIncrement> | ||||
| </template> | ||||
| @ -0,0 +1,16 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { NumberFieldInput } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   class?: HTMLAttributes['class'] | ||||
| }>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <NumberFieldInput | ||||
|     data-slot="input" | ||||
|     :class="cn('flex h-9 w-full rounded-md border border-input bg-transparent py-1 text-sm text-center transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', props.class)" | ||||
|   /> | ||||
| </template> | ||||
| @ -0,0 +1,5 @@ | ||||
| export { default as NumberField } from './NumberField.vue' | ||||
| export { default as NumberFieldContent } from './NumberFieldContent.vue' | ||||
| export { default as NumberFieldDecrement } from './NumberFieldDecrement.vue' | ||||
| export { default as NumberFieldIncrement } from './NumberFieldIncrement.vue' | ||||
| export { default as NumberFieldInput } from './NumberFieldInput.vue' | ||||
							
								
								
									
										15
									
								
								packages/app/src/modules/ui/components/select/Select.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/app/src/modules/ui/components/select/Select.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { SelectRootEmits, SelectRootProps } from 'radix-vue' | ||||
| import { SelectRoot, useForwardPropsEmits } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<SelectRootProps>() | ||||
| const emits = defineEmits<SelectRootEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectRoot v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </SelectRoot> | ||||
| </template> | ||||
| @ -0,0 +1,53 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { | ||||
|   SelectContent, | ||||
|   type SelectContentEmits, | ||||
|   type SelectContentProps, | ||||
|   SelectPortal, | ||||
|   SelectViewport, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| import { SelectScrollDownButton, SelectScrollUpButton } from '.' | ||||
| 
 | ||||
| defineOptions({ | ||||
|   inheritAttrs: false, | ||||
| }) | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<SelectContentProps & { class?: HTMLAttributes['class'] }>(), | ||||
|   { | ||||
|     position: 'popper', | ||||
|   }, | ||||
| ) | ||||
| const emits = defineEmits<SelectContentEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectPortal> | ||||
|     <SelectContent | ||||
|       v-bind="{ ...forwarded, ...$attrs }" :class="cn( | ||||
|         'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover 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', | ||||
|         position === 'popper' | ||||
|           && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', | ||||
|         props.class, | ||||
|       ) | ||||
|       " | ||||
|     > | ||||
|       <SelectScrollUpButton /> | ||||
|       <SelectViewport :class="cn('p-1', position === 'popper' && 'h-[--radix-select-trigger-height] w-full min-w-[--radix-select-trigger-width]')"> | ||||
|         <slot /> | ||||
|       </SelectViewport> | ||||
|       <SelectScrollDownButton /> | ||||
|     </SelectContent> | ||||
|   </SelectPortal> | ||||
| </template> | ||||
| @ -0,0 +1,19 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { SelectGroup, type SelectGroupProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SelectGroupProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectGroup :class="cn('p-1 w-full', props.class)" v-bind="delegatedProps"> | ||||
|     <slot /> | ||||
|   </SelectGroup> | ||||
| </template> | ||||
							
								
								
									
										44
									
								
								packages/app/src/modules/ui/components/select/SelectItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/app/src/modules/ui/components/select/SelectItem.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { CheckIcon } from '@radix-icons/vue' | ||||
| import { | ||||
|   SelectItem, | ||||
|   SelectItemIndicator, | ||||
|   type SelectItemProps, | ||||
|   SelectItemText, | ||||
|   useForwardProps, | ||||
| } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SelectItemProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectItem | ||||
|     v-bind="forwardedProps" | ||||
|     :class=" | ||||
|       cn( | ||||
|         'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | ||||
|         props.class, | ||||
|       ) | ||||
|     " | ||||
|   > | ||||
|     <span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> | ||||
|       <SelectItemIndicator> | ||||
|         <CheckIcon class="h-4 w-4" /> | ||||
|       </SelectItemIndicator> | ||||
|     </span> | ||||
| 
 | ||||
|     <SelectItemText> | ||||
|       <slot /> | ||||
|     </SelectItemText> | ||||
|   </SelectItem> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { SelectItemText, type SelectItemTextProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<SelectItemTextProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectItemText v-bind="props"> | ||||
|     <slot /> | ||||
|   </SelectItemText> | ||||
| </template> | ||||
| @ -0,0 +1,13 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { SelectLabel, type SelectLabelProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<SelectLabelProps & { class?: HTMLAttributes['class'] }>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectLabel :class="cn('px-2 py-1.5 text-sm font-semibold', props.class)"> | ||||
|     <slot /> | ||||
|   </SelectLabel> | ||||
| </template> | ||||
| @ -0,0 +1,24 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ChevronDownIcon } from '@radix-icons/vue' | ||||
| import { SelectScrollDownButton, type SelectScrollDownButtonProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectScrollDownButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)"> | ||||
|     <slot> | ||||
|       <ChevronDownIcon /> | ||||
|     </slot> | ||||
|   </SelectScrollDownButton> | ||||
| </template> | ||||
| @ -0,0 +1,24 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ChevronUpIcon } from '@radix-icons/vue' | ||||
| import { SelectScrollUpButton, type SelectScrollUpButtonProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectScrollUpButton v-bind="forwardedProps" :class="cn('flex cursor-default items-center justify-center py-1', props.class)"> | ||||
|     <slot> | ||||
|       <ChevronUpIcon /> | ||||
|     </slot> | ||||
|   </SelectScrollUpButton> | ||||
| </template> | ||||
| @ -0,0 +1,17 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { SelectSeparator, type SelectSeparatorProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" /> | ||||
| </template> | ||||
| @ -0,0 +1,31 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { CaretSortIcon } from '@radix-icons/vue'; | ||||
| import { SelectIcon, SelectTrigger, type SelectTriggerProps, useForwardProps } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<SelectTriggerProps & { class?: HTMLAttributes['class'] }>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectTrigger | ||||
|     v-bind="forwardedProps" | ||||
|     :class="cn( | ||||
|       'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start', | ||||
|       props.class, | ||||
|     )" | ||||
|   > | ||||
|     <slot /> | ||||
|     <SelectIcon as-child> | ||||
|       <CaretSortIcon class="w-4 h-4 opacity-50 shrink-0" /> | ||||
|     </SelectIcon> | ||||
|   </SelectTrigger> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { SelectValue, type SelectValueProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<SelectValueProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SelectValue v-bind="props"> | ||||
|     <slot /> | ||||
|   </SelectValue> | ||||
| </template> | ||||
							
								
								
									
										11
									
								
								packages/app/src/modules/ui/components/select/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/app/src/modules/ui/components/select/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| export { default as Select } from './Select.vue' | ||||
| export { default as SelectContent } from './SelectContent.vue' | ||||
| export { default as SelectGroup } from './SelectGroup.vue' | ||||
| export { default as SelectItem } from './SelectItem.vue' | ||||
| export { default as SelectItemText } from './SelectItemText.vue' | ||||
| export { default as SelectLabel } from './SelectLabel.vue' | ||||
| export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue' | ||||
| export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue' | ||||
| export { default as SelectSeparator } from './SelectSeparator.vue' | ||||
| export { default as SelectTrigger } from './SelectTrigger.vue' | ||||
| export { default as SelectValue } from './SelectValue.vue' | ||||
							
								
								
									
										14
									
								
								packages/app/src/modules/ui/components/sheet/Sheet.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/app/src/modules/ui/components/sheet/Sheet.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogRootProps>() | ||||
| const emits = defineEmits<DialogRootEmits>() | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(props, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogRoot v-bind="forwarded"> | ||||
|     <slot /> | ||||
|   </DialogRoot> | ||||
| </template> | ||||
							
								
								
									
										11
									
								
								packages/app/src/modules/ui/components/sheet/SheetClose.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/app/src/modules/ui/components/sheet/SheetClose.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogClose, type DialogCloseProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogCloseProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogClose v-bind="props"> | ||||
|     <slot /> | ||||
|   </DialogClose> | ||||
| </template> | ||||
| @ -0,0 +1,55 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { | ||||
|   DialogClose, | ||||
|   DialogContent, | ||||
|   type DialogContentEmits, | ||||
|   type DialogContentProps, | ||||
|   DialogOverlay, | ||||
|   DialogPortal, | ||||
|   useForwardPropsEmits, | ||||
| } from 'radix-vue'; | ||||
| import { computed, type HTMLAttributes } from 'vue'; | ||||
| import { type SheetVariants, sheetVariants } from '.'; | ||||
| 
 | ||||
| type SheetContentProps = { | ||||
|   class?: HTMLAttributes['class']; | ||||
|   side?: SheetVariants['side']; | ||||
| } & DialogContentProps; | ||||
| 
 | ||||
| defineOptions({ | ||||
|   inheritAttrs: false, | ||||
| }); | ||||
| 
 | ||||
| const props = defineProps<SheetContentProps>(); | ||||
| 
 | ||||
| const emits = defineEmits<DialogContentEmits>(); | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, side: __, ...delegated } = props; | ||||
| 
 | ||||
|   return delegated; | ||||
| }); | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogPortal> | ||||
|     <DialogOverlay | ||||
|       class="fixed inset-0 z-50 bg-black/30 backdrop-blur-sm dark:bg-black/30  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" | ||||
|     /> | ||||
|     <DialogContent | ||||
|       :class="cn(sheetVariants({ side }), props.class)" | ||||
|       v-bind="{ ...forwarded, ...$attrs }" | ||||
|     > | ||||
|       <slot /> | ||||
| 
 | ||||
|       <DialogClose | ||||
|         class="absolute leading-none size-8 right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary" | ||||
|       > | ||||
|         <Icon name="i-tabler-x" class="w-4 h-4" /> | ||||
|       </DialogClose> | ||||
|     </DialogContent> | ||||
|   </DialogPortal> | ||||
| </template> | ||||
| @ -0,0 +1,22 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DialogDescription, type DialogDescriptionProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogDescription | ||||
|     :class="cn('text-sm text-muted-foreground', props.class)" | ||||
|     v-bind="delegatedProps" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DialogDescription> | ||||
| </template> | ||||
							
								
								
									
										19
									
								
								packages/app/src/modules/ui/components/sheet/SheetFooter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/app/src/modules/ui/components/sheet/SheetFooter.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| <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> | ||||
|   <div | ||||
|     :class=" | ||||
|       cn( | ||||
|         'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2', | ||||
|         props.class, | ||||
|       ) | ||||
|     " | ||||
|   > | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										16
									
								
								packages/app/src/modules/ui/components/sheet/SheetHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/app/src/modules/ui/components/sheet/SheetHeader.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| <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> | ||||
|   <div | ||||
|     :class=" | ||||
|       cn('flex flex-col gap-y-2 text-center sm:text-left', props.class) | ||||
|     " | ||||
|   > | ||||
|     <slot /> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										22
									
								
								packages/app/src/modules/ui/components/sheet/SheetTitle.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/app/src/modules/ui/components/sheet/SheetTitle.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { DialogTitle, type DialogTitleProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogTitle | ||||
|     :class="cn('text-lg font-semibold text-foreground', props.class)" | ||||
|     v-bind="delegatedProps" | ||||
|   > | ||||
|     <slot /> | ||||
|   </DialogTitle> | ||||
| </template> | ||||
| @ -0,0 +1,11 @@ | ||||
| <script setup lang="ts"> | ||||
| import { DialogTrigger, type DialogTriggerProps } from 'radix-vue' | ||||
| 
 | ||||
| const props = defineProps<DialogTriggerProps>() | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <DialogTrigger v-bind="props"> | ||||
|     <slot /> | ||||
|   </DialogTrigger> | ||||
| </template> | ||||
							
								
								
									
										31
									
								
								packages/app/src/modules/ui/components/sheet/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/app/src/modules/ui/components/sheet/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| import { cva, type VariantProps } from 'class-variance-authority' | ||||
| 
 | ||||
| export { default as Sheet } from './Sheet.vue' | ||||
| export { default as SheetClose } from './SheetClose.vue' | ||||
| export { default as SheetContent } from './SheetContent.vue' | ||||
| export { default as SheetDescription } from './SheetDescription.vue' | ||||
| export { default as SheetFooter } from './SheetFooter.vue' | ||||
| export { default as SheetHeader } from './SheetHeader.vue' | ||||
| export { default as SheetTitle } from './SheetTitle.vue' | ||||
| export { default as SheetTrigger } from './SheetTrigger.vue' | ||||
| 
 | ||||
| export const sheetVariants = cva( | ||||
|   'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500', | ||||
|   { | ||||
|     variants: { | ||||
|       side: { | ||||
|         top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top', | ||||
|         bottom: | ||||
|             'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom', | ||||
|         left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm', | ||||
|         right: | ||||
|             'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm', | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       side: 'right', | ||||
|     }, | ||||
|   }, | ||||
| ) | ||||
| 
 | ||||
| export type SheetVariants = VariantProps<typeof sheetVariants> | ||||
							
								
								
									
										36
									
								
								packages/app/src/modules/ui/components/slider/Slider.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/app/src/modules/ui/components/slider/Slider.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { SliderRootEmits, SliderRootProps } from 'radix-vue' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { SliderRange, SliderRoot, SliderThumb, SliderTrack, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| 
 | ||||
| const props = defineProps<SliderRootProps & { class?: HTMLAttributes['class'] }>() | ||||
| const emits = defineEmits<SliderRootEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <SliderRoot | ||||
|     :class="cn( | ||||
|       'relative flex w-full touch-none select-none items-center data-[orientation=vertical]:flex-col data-[orientation=vertical]:w-1.5 data-[orientation=vertical]:h-full', | ||||
|       props.class, | ||||
|     )" | ||||
|     v-bind="forwarded" | ||||
|   > | ||||
|     <SliderTrack class="relative h-1.5 w-full data-[orientation=vertical]:w-1.5 grow overflow-hidden rounded-full bg-primary/20"> | ||||
|       <SliderRange class="absolute h-full data-[orientation=vertical]:w-full bg-primary" /> | ||||
|     </SliderTrack> | ||||
|     <SliderThumb | ||||
|       v-for="(_, key) in modelValue" | ||||
|       :key="key" | ||||
|       class="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" | ||||
|     /> | ||||
|   </SliderRoot> | ||||
| </template> | ||||
							
								
								
									
										1
									
								
								packages/app/src/modules/ui/components/slider/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/app/src/modules/ui/components/slider/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| export { default as Slider } from './Slider.vue' | ||||
							
								
								
									
										24
									
								
								packages/app/src/modules/ui/components/textarea/Textarea.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/app/src/modules/ui/components/textarea/Textarea.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { HTMLAttributes } from 'vue'; | ||||
| import { cn } from '@/src/modules/shared/style/cn'; | ||||
| import { useVModel } from '@vueuse/core'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   class?: HTMLAttributes['class']; | ||||
|   defaultValue?: string | number; | ||||
|   modelValue?: string | number; | ||||
| }>(); | ||||
| 
 | ||||
| const emits = defineEmits<{ | ||||
|   (e: 'update:modelValue', payload: string | number): void; | ||||
| }>(); | ||||
| 
 | ||||
| const modelValue = useVModel(props, 'modelValue', emits, { | ||||
|   passive: true, | ||||
|   defaultValue: props.defaultValue, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <textarea v-model="modelValue" :class="cn('flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', props.class)" /> | ||||
| </template> | ||||
							
								
								
									
										1
									
								
								packages/app/src/modules/ui/components/textarea/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/app/src/modules/ui/components/textarea/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| export { default as Textarea } from './Textarea.vue' | ||||
| @ -0,0 +1,34 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { toggleVariants } from '@/src/modules/ui/components/toggle' | ||||
| import type { VariantProps } from 'class-variance-authority' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ToggleGroupRoot, type ToggleGroupRootEmits, type ToggleGroupRootProps, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes, provide } from 'vue' | ||||
| 
 | ||||
| type ToggleGroupVariants = VariantProps<typeof toggleVariants> | ||||
| 
 | ||||
| const props = defineProps<ToggleGroupRootProps & { | ||||
|   class?: HTMLAttributes['class'] | ||||
|   variant?: ToggleGroupVariants['variant'] | ||||
|   size?: ToggleGroupVariants['size'] | ||||
| }>() | ||||
| const emits = defineEmits<ToggleGroupRootEmits>() | ||||
| 
 | ||||
| provide('toggleGroup', { | ||||
|   variant: props.variant, | ||||
|   size: props.size, | ||||
| }) | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, ...delegated } = props | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ToggleGroupRoot v-bind="forwarded" :class="cn('flex items-center justify-center gap-1', props.class)"> | ||||
|     <slot /> | ||||
|   </ToggleGroupRoot> | ||||
| </template> | ||||
| @ -0,0 +1,35 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { VariantProps } from 'class-variance-authority' | ||||
| import { toggleVariants } from '@/src/modules/ui/components/toggle' | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { ToggleGroupItem, type ToggleGroupItemProps, useForwardProps } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes, inject } from 'vue' | ||||
| 
 | ||||
| type ToggleGroupVariants = VariantProps<typeof toggleVariants> | ||||
| 
 | ||||
| const props = defineProps<ToggleGroupItemProps & { | ||||
|   class?: HTMLAttributes['class'] | ||||
|   variant?: ToggleGroupVariants['variant'] | ||||
|   size?: ToggleGroupVariants['size'] | ||||
| }>() | ||||
| 
 | ||||
| const context = inject<ToggleGroupVariants>('toggleGroup') | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, variant, size, ...delegated } = props | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwardedProps = useForwardProps(delegatedProps) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ToggleGroupItem | ||||
|     v-bind="forwardedProps" :class="cn(toggleVariants({ | ||||
|       variant: context?.variant || variant, | ||||
|       size: context?.size || size, | ||||
|     }), props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </ToggleGroupItem> | ||||
| </template> | ||||
| @ -0,0 +1,2 @@ | ||||
| export { default as ToggleGroup } from './ToggleGroup.vue' | ||||
| export { default as ToggleGroupItem } from './ToggleGroupItem.vue' | ||||
							
								
								
									
										35
									
								
								packages/app/src/modules/ui/components/toggle/Toggle.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								packages/app/src/modules/ui/components/toggle/Toggle.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| <script setup lang="ts"> | ||||
| import { cn } from '@/src/modules/shared/style/cn' | ||||
| import { Toggle, type ToggleEmits, type ToggleProps, useForwardPropsEmits } from 'radix-vue' | ||||
| import { computed, type HTMLAttributes } from 'vue' | ||||
| import { type ToggleVariants, toggleVariants } from '.' | ||||
| 
 | ||||
| const props = withDefaults(defineProps<ToggleProps & { | ||||
|   class?: HTMLAttributes['class'] | ||||
|   variant?: ToggleVariants['variant'] | ||||
|   size?: ToggleVariants['size'] | ||||
| }>(), { | ||||
|   variant: 'default', | ||||
|   size: 'default', | ||||
|   disabled: false, | ||||
| }) | ||||
| 
 | ||||
| const emits = defineEmits<ToggleEmits>() | ||||
| 
 | ||||
| const delegatedProps = computed(() => { | ||||
|   const { class: _, size, variant, ...delegated } = props | ||||
| 
 | ||||
|   return delegated | ||||
| }) | ||||
| 
 | ||||
| const forwarded = useForwardPropsEmits(delegatedProps, emits) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Toggle | ||||
|     v-bind="forwarded" | ||||
|     :class="cn(toggleVariants({ variant, size }), props.class)" | ||||
|   > | ||||
|     <slot /> | ||||
|   </Toggle> | ||||
| </template> | ||||
							
								
								
									
										27
									
								
								packages/app/src/modules/ui/components/toggle/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/app/src/modules/ui/components/toggle/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| import { cva, type VariantProps } from 'class-variance-authority'; | ||||
| 
 | ||||
| export { default as Toggle } from './Toggle.vue'; | ||||
| 
 | ||||
| export const toggleVariants = cva( | ||||
|   'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground', | ||||
|   { | ||||
|     variants: { | ||||
|       variant: { | ||||
|         default: 'bg-transparent', | ||||
|         outline: | ||||
|           'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground', | ||||
|       }, | ||||
|       size: { | ||||
|         default: 'h-9 px-3', | ||||
|         sm: 'h-8 px-2', | ||||
|         lg: 'h-10 px-3', | ||||
|       }, | ||||
|     }, | ||||
|     defaultVariants: { | ||||
|       variant: 'default', | ||||
|       size: 'default', | ||||
|     }, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| export type ToggleVariants = VariantProps<typeof toggleVariants>; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user