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 { CopyButton } from '@/modules/shared/copy/copy-button'; | ||||||
| import { createRefreshableSignal } from '@/modules/shared/signals'; | import { createRefreshableSignal } from '@/modules/shared/signals'; | ||||||
| import { Button } from '@/modules/ui/components/button'; | 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 { useCurrentTool } from '../../tools.provider'; | ||||||
| import defaultDictionary from './locales/en.json'; | import defaultDictionary from './locales/en.json'; | ||||||
| import { generateRandomPort } from './random-port-generator.services'; | import { generateRandomPort } from './random-port-generator.services'; | ||||||
| 
 | 
 | ||||||
| const RandomPortGenerator: Component = () => { | const RandomPortGenerator: Component = () => { | ||||||
|   const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort); |   const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort); | ||||||
|   const { t } = useCurrentTool({ defaultDictionary }); |   const { t, getTool } = useCurrentTool({ defaultDictionary }); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div class="mx-auto max-w-1200px p-6"> |  | ||||||
|     <div> |     <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> | ||||||
| 
 | 
 | ||||||
|       <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"> |               <Button onClick={refreshPort} variant="outline"> | ||||||
|                 <div class="i-tabler-refresh mr-2 text-base text-muted-foreground" /> |                 <div class="i-tabler-refresh mr-2 text-base text-muted-foreground" /> | ||||||
|                 {t('refresh')} |                 {t('refresh')} | ||||||
| @ -24,6 +39,9 @@ const RandomPortGenerator: Component = () => { | |||||||
| 
 | 
 | ||||||
|               <CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} /> |               <CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} /> | ||||||
|             </div> |             </div> | ||||||
|  |           </CardHeader> | ||||||
|  |         </Card> | ||||||
|  |       </div> | ||||||
|     </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 { Switch, SwitchControl, SwitchLabel, SwitchThumb } from '@/modules/ui/components/switch'; | ||||||
| import { TextArea } from '@/modules/ui/components/textarea'; | import { TextArea } from '@/modules/ui/components/textarea'; | ||||||
| import { TextFieldRoot } from '@/modules/ui/components/textfield'; | import { TextFieldRoot } from '@/modules/ui/components/textfield'; | ||||||
| import { type Component, createSignal } from 'solid-js'; | import { type Component, createSignal } from 'solid-js'; | ||||||
|  | import { ToolHeader } from '../../components/tool-header'; | ||||||
| import { useCurrentTool } from '../../tools.provider'; | import { useCurrentTool } from '../../tools.provider'; | ||||||
| import defaultDictionary from './locales/en.json'; | import defaultDictionary from './locales/en.json'; | ||||||
| import { createToken } from './token-generator.models'; | import { createToken } from './token-generator.models'; | ||||||
| @ -13,19 +18,30 @@ const TokenGeneratorTool: Component = () => { | |||||||
|   const [getUseSpecialCharacters, setUseSpecialCharacters] = createSignal(false); |   const [getUseSpecialCharacters, setUseSpecialCharacters] = createSignal(false); | ||||||
|   const [getLength] = createSignal(64); |   const [getLength] = createSignal(64); | ||||||
| 
 | 
 | ||||||
|   const { t } = useCurrentTool({ defaultDictionary }); |   const { t, getTool } = useCurrentTool({ defaultDictionary }); | ||||||
| 
 | 
 | ||||||
|   const getToken = () => createToken({ |   const [getToken, refreshToken] = createRefreshableSignal(() => createToken({ | ||||||
|     withUppercase: getUseUpperCase(), |     withUppercase: getUseUpperCase(), | ||||||
|     withLowercase: getUseLowerCase(), |     withLowercase: getUseLowerCase(), | ||||||
|     withNumbers: getUseNumbers(), |     withNumbers: getUseNumbers(), | ||||||
|     withSymbols: getUseSpecialCharacters(), |     withSymbols: getUseSpecialCharacters(), | ||||||
|     length: getLength(), |     length: getLength(), | ||||||
|   }); |   })); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div class="space-y-2 mx-auto max-w-1200px p-6"> |     <div> | ||||||
|       <Switch class="flex items-center space-x-2" checked={getUseUpperCase()} onChange={setUseUpperCase}> |       <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> |               <SwitchControl> | ||||||
|                 <SwitchThumb /> |                 <SwitchThumb /> | ||||||
|               </SwitchControl> |               </SwitchControl> | ||||||
| @ -34,7 +50,7 @@ const TokenGeneratorTool: Component = () => { | |||||||
|               </SwitchLabel> |               </SwitchLabel> | ||||||
|             </Switch> |             </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> |               <SwitchControl> | ||||||
|                 <SwitchThumb /> |                 <SwitchThumb /> | ||||||
|               </SwitchControl> |               </SwitchControl> | ||||||
| @ -43,7 +59,7 @@ const TokenGeneratorTool: Component = () => { | |||||||
|               </SwitchLabel> |               </SwitchLabel> | ||||||
|             </Switch> |             </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> |               <SwitchControl> | ||||||
|                 <SwitchThumb /> |                 <SwitchThumb /> | ||||||
|               </SwitchControl> |               </SwitchControl> | ||||||
| @ -52,7 +68,7 @@ const TokenGeneratorTool: Component = () => { | |||||||
|               </SwitchLabel> |               </SwitchLabel> | ||||||
|             </Switch> |             </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> |               <SwitchControl> | ||||||
|                 <SwitchThumb /> |                 <SwitchThumb /> | ||||||
|               </SwitchControl> |               </SwitchControl> | ||||||
| @ -60,10 +76,32 @@ const TokenGeneratorTool: Component = () => { | |||||||
|                 {t('symbols')} |                 {t('symbols')} | ||||||
|               </SwitchLabel> |               </SwitchLabel> | ||||||
|             </Switch> |             </Switch> | ||||||
|  |           </CardContent> | ||||||
| 
 | 
 | ||||||
|       <TextFieldRoot> |         </Card> | ||||||
|         <TextArea placeholder="Your token will appear here" value={getToken()} readonly /> | 
 | ||||||
|       </TextFieldRoot> |         <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> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ import { getToolDefinitionBySlug } from '../tools.registry'; | |||||||
| 
 | 
 | ||||||
| export const ToolPage: Component = () => { | export const ToolPage: Component = () => { | ||||||
|   const params = useParams(); |   const params = useParams(); | ||||||
|   const { getLocale } = useI18n(); |   const { getLocale, t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|   const toolDefinition = getToolDefinitionBySlug({ slug: params.toolSlug }); |   const toolDefinition = getToolDefinitionBySlug({ slug: params.toolSlug }); | ||||||
|   const ToolComponent = lazy(toolDefinition.entryFile); |   const ToolComponent = lazy(toolDefinition.entryFile); | ||||||
| @ -25,7 +25,16 @@ export const ToolPage: Component = () => { | |||||||
|   return ( |   return ( | ||||||
|     <Show when={toolDict()}> |     <Show when={toolDict()}> | ||||||
|       {toolLocaleDict => ( |       {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 /> |           <ToolComponent /> | ||||||
|         </CurrentToolProvider> |         </CurrentToolProvider> | ||||||
|       )} |       )} | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| import type { Accessor, ParentComponent } from 'solid-js'; | import type { Accessor, ParentComponent } from 'solid-js'; | ||||||
|  | import type { ToolDefinition } from './tools.types'; | ||||||
| import { flatten, translator } from '@solid-primitives/i18n'; | import { flatten, translator } from '@solid-primitives/i18n'; | ||||||
| import { merge } from 'lodash-es'; | import { merge } from 'lodash-es'; | ||||||
| import { createContext, useContext } from 'solid-js'; | import { createContext, useContext } from 'solid-js'; | ||||||
| 
 | 
 | ||||||
| type ToolProviderContext = { | type ToolProviderContext = { | ||||||
|   toolLocaleDict: Accessor<Record<string, string>>; |   toolLocaleDict: Accessor<Record<string, string>>; | ||||||
|  |   tool: Accessor<Pick<ToolDefinition, 'icon' | 'dirName' | 'createdAt'> & { name: string; description: string }>; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const CurrentToolContext = createContext<ToolProviderContext>(); | 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'); |     throw new Error('useCurrentTool must be used within a CurrentToolProvider'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const { toolLocaleDict, tool } = context; | ||||||
|  | 
 | ||||||
|   return { |   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 { 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 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