feat(ui): tool header
This commit is contained in:
		
							parent
							
								
									161b9e6bca
								
							
						
					
					
						commit
						00fd51a8e3
					
				
							
								
								
									
										30
									
								
								packages/app/src/modules/tools/components/tool-header.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								packages/app/src/modules/tools/components/tool-header.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import type { Component, ParentComponent } from 'solid-js'; | ||||
| import { cn } from '@/modules/ui/utils/cn'; | ||||
| 
 | ||||
| export const ToolHeader: ParentComponent<{ name: string; description: string; icon: string }> = (props) => { | ||||
|   return ( | ||||
|     <div> | ||||
|       <div class="w-full bg-[linear-gradient(to_right,#80808010_1px,transparent_1px),linear-gradient(to_bottom,#80808010_1px,transparent_1px)] bg-[size:48px_48px] pt-12"> | ||||
| 
 | ||||
|         <div class="flex gap-4 mb-8 max-w-1200px mx-auto px-6 items-start flex-col md:flex-row md:items-center"> | ||||
|           <div class="bg-card p-4 rounded-lg"> | ||||
|             <div class={cn(props.icon, 'size-8 md:size-12 text-muted-foreground')} /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div> | ||||
|             <h1 class="text-xl font-semibold"> | ||||
|               {props.name} | ||||
|             </h1> | ||||
|             <div class="text-muted-foreground text-base"> | ||||
|               {props.description} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="bg-gradient-to-t dark:from-background to-transparent h-24 mt-12 mb--24"></div> | ||||
|       </div> | ||||
| 
 | ||||
|       {props.children} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @ -2,21 +2,36 @@ import type { Component } from 'solid-js'; | ||||
| import { CopyButton } from '@/modules/shared/copy/copy-button'; | ||||
| import { createRefreshableSignal } from '@/modules/shared/signals'; | ||||
| import { Button } from '@/modules/ui/components/button'; | ||||
| import { Card, CardContent, CardHeader } from '@/modules/ui/components/card'; | ||||
| import { ToolHeader } from '../../components/tool-header'; | ||||
| import { useCurrentTool } from '../../tools.provider'; | ||||
| import defaultDictionary from './locales/en.json'; | ||||
| import { generateRandomPort } from './random-port-generator.services'; | ||||
| 
 | ||||
| const RandomPortGenerator: Component = () => { | ||||
|   const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort); | ||||
|   const { t } = useCurrentTool({ defaultDictionary }); | ||||
|   const { t, getTool } = useCurrentTool({ defaultDictionary }); | ||||
| 
 | ||||
|   return ( | ||||
|     <div class="mx-auto max-w-1200px p-6"> | ||||
|     <div> | ||||
|         {getPort()} | ||||
|       <ToolHeader {...getTool()} /> | ||||
| 
 | ||||
|       <div class="max-w-600px mx-auto px-6"> | ||||
|         <Card> | ||||
|           <CardHeader class="flex justify-between items-center"> | ||||
|             <div class="my-6 text-center"> | ||||
| 
 | ||||
|               <div class="text-base text-muted-foreground mb-2"> | ||||
|                 Random port: | ||||
|               </div> | ||||
| 
 | ||||
|       <div class="flex gap-4 mt-4"> | ||||
|               <div class="text-4xl font-mono"> | ||||
| 
 | ||||
|                 {getPort()} | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="flex gap-2 md:gap-4 mt-4 flex-col md:flex-row w-full justify-center"> | ||||
|               <Button onClick={refreshPort} variant="outline"> | ||||
|                 <div class="i-tabler-refresh mr-2 text-base text-muted-foreground" /> | ||||
|                 {t('refresh')} | ||||
| @ -24,6 +39,9 @@ const RandomPortGenerator: Component = () => { | ||||
| 
 | ||||
|               <CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} /> | ||||
|             </div> | ||||
|           </CardHeader> | ||||
|         </Card> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| @ -1,7 +1,12 @@ | ||||
| import { CopyButton } from '@/modules/shared/copy/copy-button'; | ||||
| import { createRefreshableSignal } from '@/modules/shared/signals'; | ||||
| import { Button } from '@/modules/ui/components/button'; | ||||
| import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/modules/ui/components/card'; | ||||
| import { Switch, SwitchControl, SwitchLabel, SwitchThumb } from '@/modules/ui/components/switch'; | ||||
| import { TextArea } from '@/modules/ui/components/textarea'; | ||||
| import { TextFieldRoot } from '@/modules/ui/components/textfield'; | ||||
| import { type Component, createSignal } from 'solid-js'; | ||||
| import { ToolHeader } from '../../components/tool-header'; | ||||
| import { useCurrentTool } from '../../tools.provider'; | ||||
| import defaultDictionary from './locales/en.json'; | ||||
| import { createToken } from './token-generator.models'; | ||||
| @ -13,19 +18,30 @@ const TokenGeneratorTool: Component = () => { | ||||
|   const [getUseSpecialCharacters, setUseSpecialCharacters] = createSignal(false); | ||||
|   const [getLength] = createSignal(64); | ||||
| 
 | ||||
|   const { t } = useCurrentTool({ defaultDictionary }); | ||||
|   const { t, getTool } = useCurrentTool({ defaultDictionary }); | ||||
| 
 | ||||
|   const getToken = () => createToken({ | ||||
|   const [getToken, refreshToken] = createRefreshableSignal(() => createToken({ | ||||
|     withUppercase: getUseUpperCase(), | ||||
|     withLowercase: getUseLowerCase(), | ||||
|     withNumbers: getUseNumbers(), | ||||
|     withSymbols: getUseSpecialCharacters(), | ||||
|     length: getLength(), | ||||
|   }); | ||||
|   })); | ||||
| 
 | ||||
|   return ( | ||||
|     <div class="space-y-2 mx-auto max-w-1200px p-6"> | ||||
|       <Switch class="flex items-center space-x-2" checked={getUseUpperCase()} onChange={setUseUpperCase}> | ||||
|     <div> | ||||
|       <ToolHeader {...getTool()} /> | ||||
| 
 | ||||
|       <div class="mx-auto max-w-1200px p-6 flex flex-col gap-4 md:flex-row items-start"> | ||||
|         <Card> | ||||
|           <CardHeader class="border-b border-border"> | ||||
|             <CardTitle class="text-muted-foreground"> | ||||
|               Configuration | ||||
|             </CardTitle> | ||||
|           </CardHeader> | ||||
| 
 | ||||
|           <CardContent class="pt-6 flex flex-col gap-2"> | ||||
|             <Switch class="flex items-center gap-2" checked={getUseUpperCase()} onChange={setUseUpperCase}> | ||||
|               <SwitchControl> | ||||
|                 <SwitchThumb /> | ||||
|               </SwitchControl> | ||||
| @ -34,7 +50,7 @@ const TokenGeneratorTool: Component = () => { | ||||
|               </SwitchLabel> | ||||
|             </Switch> | ||||
| 
 | ||||
|       <Switch class="flex items-center space-x-2" checked={getUseLowerCase()} onChange={setUseLowerCase}> | ||||
|             <Switch class="flex items-center gap-2" checked={getUseLowerCase()} onChange={setUseLowerCase}> | ||||
|               <SwitchControl> | ||||
|                 <SwitchThumb /> | ||||
|               </SwitchControl> | ||||
| @ -43,7 +59,7 @@ const TokenGeneratorTool: Component = () => { | ||||
|               </SwitchLabel> | ||||
|             </Switch> | ||||
| 
 | ||||
|       <Switch class="flex items-center space-x-2" checked={getUseNumbers()} onChange={setUseNumbers}> | ||||
|             <Switch class="flex items-center gap-2" checked={getUseNumbers()} onChange={setUseNumbers}> | ||||
|               <SwitchControl> | ||||
|                 <SwitchThumb /> | ||||
|               </SwitchControl> | ||||
| @ -52,7 +68,7 @@ const TokenGeneratorTool: Component = () => { | ||||
|               </SwitchLabel> | ||||
|             </Switch> | ||||
| 
 | ||||
|       <Switch class="flex items-center space-x-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}> | ||||
|             <Switch class="flex items-center gap-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}> | ||||
|               <SwitchControl> | ||||
|                 <SwitchThumb /> | ||||
|               </SwitchControl> | ||||
| @ -60,10 +76,32 @@ const TokenGeneratorTool: Component = () => { | ||||
|                 {t('symbols')} | ||||
|               </SwitchLabel> | ||||
|             </Switch> | ||||
|           </CardContent> | ||||
| 
 | ||||
|       <TextFieldRoot> | ||||
|         <TextArea placeholder="Your token will appear here" value={getToken()} readonly /> | ||||
|       </TextFieldRoot> | ||||
|         </Card> | ||||
| 
 | ||||
|         <Card class="flex-1"> | ||||
|           <CardHeader class="border-b border-border flex justify-between flex-row py-3 items-center"> | ||||
|             <CardTitle class="text-muted-foreground"> | ||||
|               Your token | ||||
|             </CardTitle> | ||||
| 
 | ||||
|             <div class="flex justify-center items-center gap-2"> | ||||
|               <Button onClick={refreshToken} variant="outline"> | ||||
|                 <div class="i-tabler-refresh mr-2 text-base text-muted-foreground" /> | ||||
|                 Refresh token | ||||
|               </Button> | ||||
| 
 | ||||
|               <CopyButton textToCopy={getToken} toastMessage={t('copy-toast')} /> | ||||
|             </div> | ||||
|           </CardHeader> | ||||
| 
 | ||||
|           <CardContent class="pt-6 text-center"> | ||||
|             {getToken()} | ||||
|           </CardContent> | ||||
| 
 | ||||
|         </Card> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| @ -7,7 +7,7 @@ import { getToolDefinitionBySlug } from '../tools.registry'; | ||||
| 
 | ||||
| export const ToolPage: Component = () => { | ||||
|   const params = useParams(); | ||||
|   const { getLocale } = useI18n(); | ||||
|   const { getLocale, t } = useI18n(); | ||||
| 
 | ||||
|   const toolDefinition = getToolDefinitionBySlug({ slug: params.toolSlug }); | ||||
|   const ToolComponent = lazy(toolDefinition.entryFile); | ||||
| @ -25,7 +25,16 @@ export const ToolPage: Component = () => { | ||||
|   return ( | ||||
|     <Show when={toolDict()}> | ||||
|       {toolLocaleDict => ( | ||||
|         <CurrentToolProvider toolLocaleDict={toolLocaleDict}> | ||||
|         <CurrentToolProvider | ||||
|           toolLocaleDict={toolLocaleDict} | ||||
|           tool={() => ({ | ||||
|             icon: toolDefinition.icon, | ||||
|             dirName: toolDefinition.dirName, | ||||
|             createdAt: toolDefinition.createdAt, | ||||
|             name: t(`tools.${toolDefinition.slug}.name` as any), | ||||
|             description: t(`tools.${toolDefinition.slug}.description` as any), | ||||
|           })} | ||||
|         > | ||||
|           <ToolComponent /> | ||||
|         </CurrentToolProvider> | ||||
|       )} | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| import type { Accessor, ParentComponent } from 'solid-js'; | ||||
| import type { ToolDefinition } from './tools.types'; | ||||
| import { flatten, translator } from '@solid-primitives/i18n'; | ||||
| import { merge } from 'lodash-es'; | ||||
| import { createContext, useContext } from 'solid-js'; | ||||
| 
 | ||||
| type ToolProviderContext = { | ||||
|   toolLocaleDict: Accessor<Record<string, string>>; | ||||
|   tool: Accessor<Pick<ToolDefinition, 'icon' | 'dirName' | 'createdAt'> & { name: string; description: string }>; | ||||
| }; | ||||
| 
 | ||||
| const CurrentToolContext = createContext<ToolProviderContext>(); | ||||
| @ -16,8 +18,11 @@ export function useCurrentTool<T>({ defaultDictionary }: { defaultDictionary: T | ||||
|     throw new Error('useCurrentTool must be used within a CurrentToolProvider'); | ||||
|   } | ||||
| 
 | ||||
|   const { toolLocaleDict, tool } = context; | ||||
| 
 | ||||
|   return { | ||||
|     t: translator(() => flatten(merge({}, defaultDictionary, context.toolLocaleDict()))), | ||||
|     t: translator(() => flatten(merge({}, defaultDictionary, toolLocaleDict()))), | ||||
|     getTool: tool, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,6 @@ | ||||
| import type { Flatten, Translator } from '@solid-primitives/i18n'; | ||||
| import type { defineTool } from './tools.models'; | ||||
| 
 | ||||
| export type ToolI18nFactory = <T extends Record<string, string>>(args: { defaultDictionary: T }) => { t: Translator<Flatten<T>> }; | ||||
| 
 | ||||
| export type ToolDefinition = ReturnType<typeof defineTool>; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user