Skip to content

Commit 00fd51a

Browse files
committed
feat(ui): tool header
1 parent 161b9e6 commit 00fd51a

File tree

6 files changed

+160
-57
lines changed

6 files changed

+160
-57
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Component, ParentComponent } from 'solid-js';
2+
import { cn } from '@/modules/ui/utils/cn';
3+
4+
export const ToolHeader: ParentComponent<{ name: string; description: string; icon: string }> = (props) => {
5+
return (
6+
<div>
7+
<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">
8+
9+
<div class="flex gap-4 mb-8 max-w-1200px mx-auto px-6 items-start flex-col md:flex-row md:items-center">
10+
<div class="bg-card p-4 rounded-lg">
11+
<div class={cn(props.icon, 'size-8 md:size-12 text-muted-foreground')} />
12+
</div>
13+
14+
<div>
15+
<h1 class="text-xl font-semibold">
16+
{props.name}
17+
</h1>
18+
<div class="text-muted-foreground text-base">
19+
{props.description}
20+
</div>
21+
</div>
22+
</div>
23+
24+
<div class="bg-gradient-to-t dark:from-background to-transparent h-24 mt-12 mb--24"></div>
25+
</div>
26+
27+
{props.children}
28+
</div>
29+
);
30+
};

packages/app/src/modules/tools/definitions/random-port-generator/random-port-generator.page.tsx

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,45 @@ import type { Component } from 'solid-js';
22
import { CopyButton } from '@/modules/shared/copy/copy-button';
33
import { createRefreshableSignal } from '@/modules/shared/signals';
44
import { Button } from '@/modules/ui/components/button';
5+
import { Card, CardContent, CardHeader } from '@/modules/ui/components/card';
6+
import { ToolHeader } from '../../components/tool-header';
57
import { useCurrentTool } from '../../tools.provider';
68
import defaultDictionary from './locales/en.json';
79
import { generateRandomPort } from './random-port-generator.services';
810

911
const RandomPortGenerator: Component = () => {
1012
const [getPort, refreshPort] = createRefreshableSignal(generateRandomPort);
11-
const { t } = useCurrentTool({ defaultDictionary });
13+
const { t, getTool } = useCurrentTool({ defaultDictionary });
1214

1315
return (
14-
<div class="mx-auto max-w-1200px p-6">
15-
<div>
16-
{getPort()}
17-
</div>
16+
<div>
17+
<ToolHeader {...getTool()} />
18+
19+
<div class="max-w-600px mx-auto px-6">
20+
<Card>
21+
<CardHeader class="flex justify-between items-center">
22+
<div class="my-6 text-center">
23+
24+
<div class="text-base text-muted-foreground mb-2">
25+
Random port:
26+
</div>
27+
28+
<div class="text-4xl font-mono">
29+
30+
{getPort()}
31+
</div>
32+
</div>
1833

19-
<div class="flex gap-4 mt-4">
20-
<Button onClick={refreshPort} variant="outline">
21-
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
22-
{t('refresh')}
23-
</Button>
34+
<div class="flex gap-2 md:gap-4 mt-4 flex-col md:flex-row w-full justify-center">
35+
<Button onClick={refreshPort} variant="outline">
36+
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
37+
{t('refresh')}
38+
</Button>
2439

25-
<CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} />
40+
<CopyButton textToCopy={getPort} toastMessage={t('copy-toast')} />
41+
</div>
42+
</CardHeader>
43+
</Card>
2644
</div>
2745
</div>
2846
);

packages/app/src/modules/tools/definitions/token-generator/token-generator.page.tsx

Lines changed: 81 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import { CopyButton } from '@/modules/shared/copy/copy-button';
2+
import { createRefreshableSignal } from '@/modules/shared/signals';
3+
import { Button } from '@/modules/ui/components/button';
4+
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/modules/ui/components/card';
15
import { Switch, SwitchControl, SwitchLabel, SwitchThumb } from '@/modules/ui/components/switch';
26
import { TextArea } from '@/modules/ui/components/textarea';
37
import { TextFieldRoot } from '@/modules/ui/components/textfield';
48
import { type Component, createSignal } from 'solid-js';
9+
import { ToolHeader } from '../../components/tool-header';
510
import { useCurrentTool } from '../../tools.provider';
611
import defaultDictionary from './locales/en.json';
712
import { createToken } from './token-generator.models';
@@ -13,57 +18,90 @@ const TokenGeneratorTool: Component = () => {
1318
const [getUseSpecialCharacters, setUseSpecialCharacters] = createSignal(false);
1419
const [getLength] = createSignal(64);
1520

16-
const { t } = useCurrentTool({ defaultDictionary });
21+
const { t, getTool } = useCurrentTool({ defaultDictionary });
1722

18-
const getToken = () => createToken({
23+
const [getToken, refreshToken] = createRefreshableSignal(() => createToken({
1924
withUppercase: getUseUpperCase(),
2025
withLowercase: getUseLowerCase(),
2126
withNumbers: getUseNumbers(),
2227
withSymbols: getUseSpecialCharacters(),
2328
length: getLength(),
24-
});
29+
}));
2530

2631
return (
27-
<div class="space-y-2 mx-auto max-w-1200px p-6">
28-
<Switch class="flex items-center space-x-2" checked={getUseUpperCase()} onChange={setUseUpperCase}>
29-
<SwitchControl>
30-
<SwitchThumb />
31-
</SwitchControl>
32-
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
33-
{t('uppercase')}
34-
</SwitchLabel>
35-
</Switch>
36-
37-
<Switch class="flex items-center space-x-2" checked={getUseLowerCase()} onChange={setUseLowerCase}>
38-
<SwitchControl>
39-
<SwitchThumb />
40-
</SwitchControl>
41-
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
42-
{t('lowercase')}
43-
</SwitchLabel>
44-
</Switch>
45-
46-
<Switch class="flex items-center space-x-2" checked={getUseNumbers()} onChange={setUseNumbers}>
47-
<SwitchControl>
48-
<SwitchThumb />
49-
</SwitchControl>
50-
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
51-
{t('numbers')}
52-
</SwitchLabel>
53-
</Switch>
54-
55-
<Switch class="flex items-center space-x-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}>
56-
<SwitchControl>
57-
<SwitchThumb />
58-
</SwitchControl>
59-
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
60-
{t('symbols')}
61-
</SwitchLabel>
62-
</Switch>
63-
64-
<TextFieldRoot>
65-
<TextArea placeholder="Your token will appear here" value={getToken()} readonly />
66-
</TextFieldRoot>
32+
<div>
33+
<ToolHeader {...getTool()} />
34+
35+
<div class="mx-auto max-w-1200px p-6 flex flex-col gap-4 md:flex-row items-start">
36+
<Card>
37+
<CardHeader class="border-b border-border">
38+
<CardTitle class="text-muted-foreground">
39+
Configuration
40+
</CardTitle>
41+
</CardHeader>
42+
43+
<CardContent class="pt-6 flex flex-col gap-2">
44+
<Switch class="flex items-center gap-2" checked={getUseUpperCase()} onChange={setUseUpperCase}>
45+
<SwitchControl>
46+
<SwitchThumb />
47+
</SwitchControl>
48+
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
49+
{t('uppercase')}
50+
</SwitchLabel>
51+
</Switch>
52+
53+
<Switch class="flex items-center gap-2" checked={getUseLowerCase()} onChange={setUseLowerCase}>
54+
<SwitchControl>
55+
<SwitchThumb />
56+
</SwitchControl>
57+
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
58+
{t('lowercase')}
59+
</SwitchLabel>
60+
</Switch>
61+
62+
<Switch class="flex items-center gap-2" checked={getUseNumbers()} onChange={setUseNumbers}>
63+
<SwitchControl>
64+
<SwitchThumb />
65+
</SwitchControl>
66+
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
67+
{t('numbers')}
68+
</SwitchLabel>
69+
</Switch>
70+
71+
<Switch class="flex items-center gap-2" checked={getUseSpecialCharacters()} onChange={setUseSpecialCharacters}>
72+
<SwitchControl>
73+
<SwitchThumb />
74+
</SwitchControl>
75+
<SwitchLabel class="text-sm font-medium leading-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70">
76+
{t('symbols')}
77+
</SwitchLabel>
78+
</Switch>
79+
</CardContent>
80+
81+
</Card>
82+
83+
<Card class="flex-1">
84+
<CardHeader class="border-b border-border flex justify-between flex-row py-3 items-center">
85+
<CardTitle class="text-muted-foreground">
86+
Your token
87+
</CardTitle>
88+
89+
<div class="flex justify-center items-center gap-2">
90+
<Button onClick={refreshToken} variant="outline">
91+
<div class="i-tabler-refresh mr-2 text-base text-muted-foreground" />
92+
Refresh token
93+
</Button>
94+
95+
<CopyButton textToCopy={getToken} toastMessage={t('copy-toast')} />
96+
</div>
97+
</CardHeader>
98+
99+
<CardContent class="pt-6 text-center">
100+
{getToken()}
101+
</CardContent>
102+
103+
</Card>
104+
</div>
67105
</div>
68106
);
69107
};

packages/app/src/modules/tools/pages/tool.page.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getToolDefinitionBySlug } from '../tools.registry';
77

88
export const ToolPage: Component = () => {
99
const params = useParams();
10-
const { getLocale } = useI18n();
10+
const { getLocale, t } = useI18n();
1111

1212
const toolDefinition = getToolDefinitionBySlug({ slug: params.toolSlug });
1313
const ToolComponent = lazy(toolDefinition.entryFile);
@@ -25,7 +25,16 @@ export const ToolPage: Component = () => {
2525
return (
2626
<Show when={toolDict()}>
2727
{toolLocaleDict => (
28-
<CurrentToolProvider toolLocaleDict={toolLocaleDict}>
28+
<CurrentToolProvider
29+
toolLocaleDict={toolLocaleDict}
30+
tool={() => ({
31+
icon: toolDefinition.icon,
32+
dirName: toolDefinition.dirName,
33+
createdAt: toolDefinition.createdAt,
34+
name: t(`tools.${toolDefinition.slug}.name` as any),
35+
description: t(`tools.${toolDefinition.slug}.description` as any),
36+
})}
37+
>
2938
<ToolComponent />
3039
</CurrentToolProvider>
3140
)}

packages/app/src/modules/tools/tools.provider.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { Accessor, ParentComponent } from 'solid-js';
2+
import type { ToolDefinition } from './tools.types';
23
import { flatten, translator } from '@solid-primitives/i18n';
34
import { merge } from 'lodash-es';
45
import { createContext, useContext } from 'solid-js';
56

67
type ToolProviderContext = {
78
toolLocaleDict: Accessor<Record<string, string>>;
9+
tool: Accessor<Pick<ToolDefinition, 'icon' | 'dirName' | 'createdAt'> & { name: string; description: string }>;
810
};
911

1012
const CurrentToolContext = createContext<ToolProviderContext>();
@@ -16,8 +18,11 @@ export function useCurrentTool<T>({ defaultDictionary }: { defaultDictionary: T
1618
throw new Error('useCurrentTool must be used within a CurrentToolProvider');
1719
}
1820

21+
const { toolLocaleDict, tool } = context;
22+
1923
return {
20-
t: translator(() => flatten(merge({}, defaultDictionary, context.toolLocaleDict()))),
24+
t: translator(() => flatten(merge({}, defaultDictionary, toolLocaleDict()))),
25+
getTool: tool,
2126
};
2227
}
2328

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
import type { Flatten, Translator } from '@solid-primitives/i18n';
2+
import type { defineTool } from './tools.models';
23

34
export type ToolI18nFactory = <T extends Record<string, string>>(args: { defaultDictionary: T }) => { t: Translator<Flatten<T>> };
5+
6+
export type ToolDefinition = ReturnType<typeof defineTool>;

0 commit comments

Comments
 (0)