mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-24 08:46:15 -04:00
feat(ui): tool header
This commit is contained in:
parent
161b9e6bca
commit
00fd51a8e3
6 changed files with 156 additions and 53 deletions
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,27 +2,45 @@ 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()}
|
||||
</div>
|
||||
<div>
|
||||
<ToolHeader {...getTool()} />
|
||||
|
||||
<div class="flex gap-4 mt-4">
|
||||
<Button onClick={refreshPort} variant="outline">
|
||||
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
|
||||
{t('refresh')}
|
||||
</Button>
|
||||
<div class="max-w-600px mx-auto px-6">
|
||||
<Card>
|
||||
<CardHeader class="flex justify-between items-center">
|
||||
<div class="my-6 text-center">
|
||||
|
||||
<CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} />
|
||||
<div class="text-base text-muted-foreground mb-2">
|
||||
Random port:
|
||||
</div>
|
||||
|
||||
<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')}
|
||||
</Button>
|
||||
|
||||
<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,57 +18,90 @@ 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}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('uppercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
<div>
|
||||
<ToolHeader {...getTool()} />
|
||||
|
||||
<Switch class="flex items-center space-x-2" checked={getUseLowerCase()} onChange={setUseLowerCase}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('lowercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
<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>
|
||||
|
||||
<Switch class="flex items-center space-x-2" checked={getUseNumbers()} onChange={setUseNumbers}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('numbers')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
<CardContent class="pt-6 flex flex-col gap-2">
|
||||
<Switch class="flex items-center gap-2" checked={getUseUpperCase()} onChange={setUseUpperCase}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('uppercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<Switch class="flex items-center space-x-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('symbols')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
<Switch class="flex items-center gap-2" checked={getUseLowerCase()} onChange={setUseLowerCase}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('lowercase')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<TextFieldRoot>
|
||||
<TextArea placeholder="Your token will appear here" value={getToken()} readonly />
|
||||
</TextFieldRoot>
|
||||
<Switch class="flex items-center gap-2" checked={getUseNumbers()} onChange={setUseNumbers}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('numbers')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
|
||||
<Switch class="flex items-center gap-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}>
|
||||
<SwitchControl>
|
||||
<SwitchThumb />
|
||||
</SwitchControl>
|
||||
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
|
||||
{t('symbols')}
|
||||
</SwitchLabel>
|
||||
</Switch>
|
||||
</CardContent>
|
||||
|
||||
</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…
Add table
Add a link
Reference in a new issue