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> | <template> | ||||||
|   <NuxtLayout> |   <NuxtLayout> | ||||||
|  |     <CommandPalette /> | ||||||
|     <NuxtPage /> |     <NuxtPage /> | ||||||
|   </NuxtLayout> |   </NuxtLayout> | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|     --popover: 0 0% 100%; |     --popover: 0 0% 100%; | ||||||
|     --popover-foreground: 240 10% 3.9%; |     --popover-foreground: 240 10% 3.9%; | ||||||
|    |    | ||||||
|     --primary: 150 76% 38%; |     --primary: 149 79% 35%; | ||||||
|     --primary-foreground: 0 0% 98%; |     --primary-foreground: 0 0% 98%; | ||||||
|    |    | ||||||
|     --secondary: 240 4.8% 95.9%; |     --secondary: 240 4.8% 95.9%; | ||||||
| @ -36,7 +36,7 @@ | |||||||
|   } |   } | ||||||
|     |     | ||||||
|   .dark { |   .dark { | ||||||
|     --background:240 5% 6%; |     --background:240 4% 10%; | ||||||
|     --foreground:0 0% 98%; |     --foreground:0 0% 98%; | ||||||
|    |    | ||||||
|     --card: 240 5% 8%; |     --card: 240 5% 8%; | ||||||
|  | |||||||
| @ -53,4 +53,8 @@ export default defineNuxtConfig({ | |||||||
|       { code: 'fr', file: 'fr.yaml', name: 'Français' }, |       { 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: >- |     description: >- | ||||||
|       Generate random string with the characters you want, uppercase, lowercase |       Generate random string with the characters you want, uppercase, lowercase | ||||||
|       letters, numbers and/or symbols. |       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> | <script setup> | ||||||
| import { Badge } from '@/src/modules/ui/components/badge'; | import { Badge } from '@/src/modules/ui/components/badge'; | ||||||
| import { Button, buttonVariants } from '@/src/modules/ui/components/button'; | import { Button, buttonVariants } from '@/src/modules/ui/components/button'; | ||||||
|  | import { useCommandPaletteStore } from '../../command-palette/command-palette.store'; | ||||||
| import { cn } from '../../shared/style/cn'; | import { cn } from '../../shared/style/cn'; | ||||||
| import { useToolsStore } from '../../tools/tools.store'; | import { useToolsStore } from '../../tools/tools.store'; | ||||||
| import { CardContent } from '../../ui/components/card'; | import { CardContent } from '../../ui/components/card'; | ||||||
| import Card from '../../ui/components/card/Card.vue'; | import Card from '../../ui/components/card/Card.vue'; | ||||||
| 
 | 
 | ||||||
| const { tools } = useToolsStore(); | const { tools } = useToolsStore(); | ||||||
|  | const { openCommandPalette } = useCommandPaletteStore(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <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="flex gap-24 mx-auto justify-center pb-8 mt-8 items-center px-6"> | ||||||
|       <div class="max-w-xl"> |       <div class="max-w-xl"> | ||||||
|         <div class="flex gap-2"> |         <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') }} |             {{ $t('home.open-source') }} | ||||||
|           </Badge> |           </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') }} |             {{ $t('home.free') }} | ||||||
|           </Badge> |           </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') }} |             {{ $t('home.self-hostable') }} | ||||||
|           </Badge> |           </Badge> | ||||||
|         </div> |         </div> | ||||||
| @ -40,7 +42,7 @@ const { tools } = useToolsStore(); | |||||||
|             <Icon name="i-tabler-arrow-right" class="ml-2 size-4" /> |             <Icon name="i-tabler-arrow-right" class="ml-2 size-4" /> | ||||||
|           </Button> |           </Button> | ||||||
| 
 | 
 | ||||||
|           <Button variant="outline"> |           <Button variant="outline" @click="openCommandPalette"> | ||||||
|             <Icon name="i-tabler-search" class="mr-2 size-4" /> |             <Icon name="i-tabler-search" class="mr-2 size-4" /> | ||||||
|             {{ $t('home.search-tools') }} |             {{ $t('home.search-tools') }} | ||||||
|           </Button> |           </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"> | <script setup lang="ts"> | ||||||
|  | import { times } from 'lodash-es'; | ||||||
| import { useRefreshableState } from '~/src/modules/shared/composables/useRefreshableState'; | import { useRefreshableState } from '~/src/modules/shared/composables/useRefreshableState'; | ||||||
| import { Button } from '~/src/modules/ui/components/button'; | 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'; | import { createToken } from './token-generator.models'; | ||||||
| 
 | 
 | ||||||
|  | definePageMeta({ | ||||||
|  |   layout: 'sidenav', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| const withUppercase = ref(true); | const withUppercase = ref(true); | ||||||
| const withLowercase = ref(true); | const withLowercase = ref(true); | ||||||
| const withNumbers = ref(true); | const withNumbers = ref(true); | ||||||
| const withSymbols = ref(false); | const withSymbols = ref(false); | ||||||
| const length = ref(64); | const length = ref(48); | ||||||
| 
 | 
 | ||||||
| const [token, refreshToken] = useRefreshableState( | const formats = { | ||||||
|   'token-generator:token', |   raw: { | ||||||
|   () => createToken({ |     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, |     withUppercase: withUppercase.value, | ||||||
|     withLowercase: withLowercase.value, |     withLowercase: withLowercase.value, | ||||||
|     withNumbers: withNumbers.value, |     withNumbers: withNumbers.value, | ||||||
|     withSymbols: withSymbols.value, |     withSymbols: withSymbols.value, | ||||||
|     length: length.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' }); | // const { copy: copyToken } = useCopy({ source: token, notificationText: 'Token copied to clipboard' }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <div class="max-w-screen-md mx-auto p-6"> |   <div class="flex flex-col h-full"> | ||||||
|     <div>{{ token }}</div> |     <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"> |     <div class="h-full flex-1 p-6"> | ||||||
|       Generate new token |       <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> |         </Button> | ||||||
|  |       </Card> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -7,10 +7,10 @@ export const buttonVariants = cva( | |||||||
|   { |   { | ||||||
|     variants: { |     variants: { | ||||||
|       variant: { |       variant: { | ||||||
|         default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', |         default: 'bg-primary text-primary-foreground hover:bg-primary/90', | ||||||
|         destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', |         destructive: 'bg-destructive text-destructive-foreground  hover:bg-destructive/90', | ||||||
|         outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', |         outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', | ||||||
|         secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', |         secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | ||||||
|         ghost: 'hover:bg-accent hover:text-accent-foreground', |         ghost: 'hover:bg-accent hover:text-accent-foreground', | ||||||
|         link: 'text-primary underline-offset-4 hover:underline', |         link: 'text-primary underline-offset-4 hover:underline', | ||||||
|       }, |       }, | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ const props = defineProps<{ | |||||||
|   <div |   <div | ||||||
|     :class=" |     :class=" | ||||||
|       cn( |       cn( | ||||||
|         'rounded-xl border bg-card text-card-foreground shadow', |         'rounded-xl border bg-card text-card-foreground', | ||||||
|         props.class, |         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