mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -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,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…
Add table
Add a link
Reference in a new issue