Skip to content

Commit 5ae3486

Browse files
zehfernandesgraphite-app[bot]
authored andcommitted
feat: design polish v4 (#1961)
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
1 parent 597e0ce commit 5ae3486

17 files changed

+117
-99
lines changed

packages/react-email/src/components/code-container.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
7272

7373
return (
7474
<div
75-
className="relative w-full items-center whitespace-pre rounded-md border border-slate-6 text-sm backdrop-blur-md"
75+
className="relative w-full items-center whitespace-pre rounded-md border border-slate-6 text-sm"
7676
style={{
7777
lineHeight: '130%',
7878
background:

packages/react-email/src/components/code.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
6060
'linear-gradient(90deg, rgba(56, 189, 248, 0) 0%, rgba(56, 189, 248, 0) 0%, rgba(232, 232, 232, 0.2) 33.02%, rgba(143, 143, 143, 0.6719) 64.41%, rgba(236, 72, 153, 0) 98.93%)',
6161
}}
6262
/>
63-
<pre className="h-[650px] overflow-auto p-4">
63+
<pre className="overflow-auto p-4 max-h-[calc(100vh-10rem)]">
6464
{tokens.map((line, i) => {
6565
const lineProps = getLineProps({
6666
line,
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
export const IconReload = (props: React.ComponentProps<'svg'>) => {
2-
return (
3-
<svg
4-
width="12"
5-
height="12"
6-
viewBox="0 0 12 12"
7-
fill="none"
8-
xmlns="http://www.w3.org/2000/svg"
9-
{...props}
10-
>
1+
import * as React from 'react';
2+
import type { IconElement, IconProps } from './icon-base';
3+
import { IconBase } from './icon-base';
4+
5+
export const IconReload = React.forwardRef<IconElement, Readonly<IconProps>>(
6+
({ ...props }, forwardedRef) => (
7+
<IconBase ref={forwardedRef} {...props}>
118
<path
129
fillRule="evenodd"
1310
clipRule="evenodd"
14-
d="M10.52 6C10.52 3.73168 8.75221 1.48 6.00006 1.48C3.77741 1.48 2.67886 3.1251 2.21074 3.99999H3.60005C3.82096 3.99999 4.00005 4.17908 4.00005 4.39999C4.00005 4.6209 3.82096 4.79999 3.60005 4.79999H1.20005C0.979137 4.79999 0.800049 4.6209 0.800049 4.39999V1.99999C0.800049 1.77908 0.979137 1.59999 1.20005 1.59999C1.42096 1.59999 1.60005 1.77908 1.60005 1.99999V3.45056C2.16367 2.45702 3.4673 0.679993 6.00006 0.679993C9.25029 0.679993 11.32 3.34831 11.32 6C11.32 8.65169 9.25029 11.32 6.00006 11.32C4.44499 11.32 3.15027 10.7047 2.22843 9.76673C1.73486 9.26449 1.34939 8.67121 1.08658 8.03257C1.0025 7.8283 1.09995 7.59453 1.30424 7.51046C1.50853 7.42638 1.7423 7.52384 1.82637 7.72812C2.05104 8.27401 2.38001 8.77961 2.79901 9.20593C3.57646 9.99705 4.66802 10.52 6.00006 10.52C8.75221 10.52 10.52 8.26833 10.52 6Z"
11+
d="M17.9354 12C17.9354 9.01537 15.5828 6.05264 11.9202 6.05264C8.96229 6.05264 7.50033 8.21724 6.87735 9.36841H8.72625C9.02024 9.36841 9.25858 9.60406 9.25858 9.89473C9.25858 10.1854 9.02024 10.421 8.72625 10.421H5.53232C5.23833 10.421 5 10.1854 5 9.89473V6.73684C5 6.44617 5.23833 6.21052 5.53232 6.21052C5.82631 6.21052 6.06465 6.44617 6.06465 6.73684V8.64548C6.81471 7.33819 8.54959 5 11.9202 5C16.2456 5 19 8.51094 19 12C19 15.4891 16.2456 19 11.9202 19C9.8507 19 8.12769 18.1904 6.9009 16.9562C6.24405 16.2954 5.73107 15.5148 5.38132 14.6744C5.26942 14.4057 5.39911 14.0981 5.67098 13.9875C5.94285 13.8768 6.25395 14.0051 6.36583 14.2738C6.66482 14.9921 7.10262 15.6574 7.66023 16.2183C8.69486 17.2593 10.1475 17.9474 11.9202 17.9474C15.5828 17.9474 17.9354 14.9846 17.9354 12Z"
1512
fill="currentColor"
1613
/>
17-
</svg>
18-
);
19-
};
14+
</IconBase>
15+
),
16+
);
17+
18+
IconReload.displayName = 'IconReload';

packages/react-email/src/components/icons/icon-scanner.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export const IconScanner = (props: React.ComponentProps<'svg'>) => {
22
return (
33
<svg
4-
width="13"
5-
height="12"
4+
width="15"
5+
height="13"
66
viewBox="0 0 13 12"
77
fill="none"
88
xmlns="http://www.w3.org/2000/svg"

packages/react-email/src/components/icons/icon-scissors.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export const IconScissors = (props: React.ComponentProps<'svg'>) => {
22
return (
33
<svg
4-
width="12"
5-
height="10"
4+
width="14"
5+
height="12"
66
viewBox="0 0 12 10"
77
fill="none"
88
xmlns="http://www.w3.org/2000/svg"

packages/react-email/src/components/logo.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
export const Logo = () => (
22
<svg
33
fill="none"
4-
height="32"
4+
height="30"
55
viewBox="0 0 119 32"
6-
width="119"
6+
width="113"
77
xmlns="http://www.w3.org/2000/svg"
8+
style={{ opacity: 0.9 }}
89
>
910
<g clipPath="url(#clip0_27_291)">
1011
<path

packages/react-email/src/components/resizable-wrapper.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,7 @@ export const ResizableWarpper = ({
100100
return (
101101
<div
102102
{...rest}
103-
className={cn(
104-
'relative mx-auto my-auto box-content px-4 py-2',
105-
rest.className,
106-
)}
103+
className={cn('relative mx-auto my-auto box-content', rest.className)}
107104
>
108105
<div
109106
aria-label="resize-west"

packages/react-email/src/components/sidebar/sidebar.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { clsx } from 'clsx';
33
import { useEmails } from '../../contexts/emails';
44
import { cn } from '../../utils';
5-
import { Heading } from '../heading';
65
import { Logo } from '../logo';
76
import { FileTree } from './file-tree';
87

@@ -29,9 +28,9 @@ export const Sidebar = ({ className, currentEmailOpenSlug }: SidebarProps) => {
2928
'hidden min-h-[3.3125rem] flex-shrink items-center p-3 px-4 lg:flex',
3029
)}
3130
>
32-
<Heading as="h2" className="truncate" size="2" weight="medium">
31+
<h2>
3332
<Logo />
34-
</Heading>
33+
</h2>
3534
</div>
3635
<div className="relative h-full w-full border-slate-4 border-t px-4 pb-3">
3736
<FileTree

packages/react-email/src/components/toolbar.tsx

+40-16
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,18 @@ const ToolbarInner = ({
6161
useCachedState<SpamCheckingResult>(
6262
`spam-assassin-${emailSlug.replaceAll('/', '-')}`,
6363
);
64-
const [spamCheckingResult, { load: loadSpamChecking }] = useSpamAssassin({
65-
markup,
66-
plainText,
64+
const [spamCheckingResult, { load: loadSpamChecking, loading: spamLoading }] =
65+
useSpamAssassin({
66+
markup,
67+
plainText,
6768

68-
initialResult: serverSpamCheckingResult ?? cachedSpamCheckingResult,
69-
});
69+
initialResult: serverSpamCheckingResult ?? cachedSpamCheckingResult,
70+
});
7071

7172
const [cachedLintingRows, setCachedLintingRows] = useCachedState<
7273
LintingRow[]
7374
>(`linter-${emailSlug.replaceAll('/', '-')}`);
74-
const [lintingRows, { load: loadLinting }] = useLinter({
75+
const [lintingRows, { load: loadLinting, loading: lintLoading }] = useLinter({
7576
reactMarkup,
7677
emailPath,
7778
markup,
@@ -95,19 +96,20 @@ const ToolbarInner = ({
9596
<div
9697
data-toggled={toggled}
9798
className={cn(
98-
'bg-black group/toolbar text-xs text-slate-11 h-48 transition-all',
99-
'data-[toggled=false]:h-8',
99+
'absolute bottom-0 left-0 right-0',
100+
'bg-black group/toolbar text-xs text-slate-11 h-52 transition-transform',
101+
'data-[toggled=false]:translate-y-[170px]',
100102
)}
101103
>
102104
<Tabs.Root
103-
value={activeTab}
105+
value={activeTab ?? ''}
104106
onValueChange={(newValue) => {
105107
setActivePanelValue(newValue as ToolbarTabValue);
106108
}}
107109
asChild
108110
>
109111
<div className="flex flex-col h-full">
110-
<Tabs.List className="flex gap-4 px-2 border-b border-solid border-slate-6 h-7 w-full">
112+
<Tabs.List className="flex gap-4 px-2 border-b border-solid border-slate-6 h-9 w-full flex-shrink-0">
111113
<LayoutGroup id="toolbar">
112114
<Tabs.Trigger asChild value="spam-assassin">
113115
<ToolbarButton active={activeTab === 'spam-assassin'}>
@@ -122,10 +124,11 @@ const ToolbarInner = ({
122124
</ToolbarButton>
123125
</Tabs.Trigger>
124126
</LayoutGroup>
125-
<div className="flex gap-1 ml-auto">
127+
<div className="flex gap-0.5 ml-auto">
126128
{isBuilding ? null : (
127129
<ToolbarButton
128130
tooltip="Reload"
131+
disabled={lintLoading || spamLoading}
129132
onClick={async () => {
130133
if (activeTab === undefined) {
131134
setActivePanelValue('linter');
@@ -137,7 +140,13 @@ const ToolbarInner = ({
137140
}
138141
}}
139142
>
140-
<IconReload />
143+
<IconReload
144+
size={24}
145+
className={cn({
146+
'animate-spin opacity-60 animate-spin-fast':
147+
lintLoading || spamLoading,
148+
})}
149+
/>
141150
</ToolbarButton>
142151
)}
143152
<ToolbarButton
@@ -150,17 +159,32 @@ const ToolbarInner = ({
150159
}
151160
}}
152161
>
153-
<IconArrowDown className="transition-transform group-data-[toggled=false]/toolbar:rotate-180" />
162+
<IconArrowDown
163+
size={24}
164+
className="transition-transform group-data-[toggled=false]/toolbar:rotate-180"
165+
/>
154166
</ToolbarButton>
155167
</div>
156168
</Tabs.List>
157169

158-
<div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto px-2">
170+
<div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto px-2 pt-3">
159171
<Tabs.Content value="linter">
160-
<Linter rows={lintingRows} />
172+
{lintLoading ? (
173+
<div className="animate-pulse text-slate-11 text-sm pt-1">
174+
Running linting...
175+
</div>
176+
) : (
177+
<Linter rows={lintingRows} />
178+
)}
161179
</Tabs.Content>
162180
<Tabs.Content value="spam-assassin">
163-
<SpamAssassin result={spamCheckingResult} />
181+
{spamLoading ? (
182+
<div className="animate-pulse text-slate-11 text-sm pt-1">
183+
Running spam check...
184+
</div>
185+
) : (
186+
<SpamAssassin result={spamCheckingResult} />
187+
)}
164188
</Tabs.Content>
165189
</div>
166190
</div>

packages/react-email/src/components/toolbar/linter.tsx

+17-18
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { nicenames } from '../../actions/email-validation/caniemail-data';
44
import type { CompatibilityCheckingResult } from '../../actions/email-validation/check-compatibility';
55
import type { ImageCheckingResult } from '../../actions/email-validation/check-images';
66
import type { LinkCheckingResult } from '../../actions/email-validation/check-links';
7-
import { cn } from '../../utils';
7+
import { cn, sanitize } from '../../utils';
88
import { getLintingSources, loadLintingRowsFrom } from '../../utils/linting';
99
import { IconWarning } from '../icons/icon-warning';
1010
import { Results } from './results';
@@ -105,7 +105,7 @@ export const Linter = ({ rows }: LinterProps) => {
105105
)!;
106106
return (
107107
<Result status={row.result.status} key={i}>
108-
<Result.Name>{failingCheck.type}</Result.Name>
108+
<Result.Name>{sanitize(failingCheck.type)}</Result.Name>
109109
<Result.Description>
110110
{failingCheck.type === 'security'
111111
? 'Insecure URL, use HTTPS insted of HTTP'
@@ -125,7 +125,7 @@ export const Linter = ({ rows }: LinterProps) => {
125125
? 'The link is broken due to invalid syntax'
126126
: null}
127127

128-
<span className="font-mono float-right text-ellipsis overflow-hidden text-nowrap max-w-[30ch]">
128+
<span className="ml-2 text-ellipsis overflow-hidden text-nowrap max-w-[30ch]">
129129
{row.result.link}
130130
</span>
131131
</Result.Description>
@@ -148,10 +148,10 @@ export const Linter = ({ rows }: LinterProps) => {
148148
)!;
149149
return (
150150
<Result status={row.result.status} key={i}>
151-
<Result.Name>{failingCheck.type}</Result.Name>
151+
<Result.Name>{sanitize(failingCheck.type)}</Result.Name>
152152
<Result.Description>
153153
{failingCheck.type === 'security'
154-
? 'Insecure URL, use HTTPS insted of HTTP'
154+
? 'Insecure URL, use HTTPS instead of HTTP'
155155
: null}
156156
{failingCheck.type === 'fetch_attempt' &&
157157
failingCheck.metadata.fetchStatusCode &&
@@ -177,7 +177,7 @@ export const Linter = ({ rows }: LinterProps) => {
177177
? 'This image is too large, keep it under 1mb'
178178
: null}
179179

180-
<span className="font-mono float-right text-ellipsis overflow-hidden text-nowrap max-w-[30ch]">
180+
<span className="ml-2 text-ellipsis overflow-hidden text-nowrap max-w-[30ch]">
181181
{row.result.source}
182182
</span>
183183
</Result.Description>
@@ -205,7 +205,7 @@ export const Linter = ({ rows }: LinterProps) => {
205205
return undefined;
206206
})
207207
.filter(Boolean)
208-
.join('')}
208+
.join(' · ')}
209209
</Result.Metadata>
210210
</Result>
211211
);
@@ -228,7 +228,7 @@ export const Linter = ({ rows }: LinterProps) => {
228228

229229
return (
230230
<Result status={row.result.status} key={i}>
231-
<Result.Name>{row.result.entry.title}</Result.Name>
231+
<Result.Name>{sanitize(row.result.entry.title)}</Result.Name>
232232
<Result.Description>
233233
{statsReportedNotWorking.length > 0
234234
? `Not supported in ${unsupportedClientsString}`
@@ -240,20 +240,19 @@ export const Linter = ({ rows }: LinterProps) => {
240240
{statsReportedPartiallyWorking.length > 0
241241
? `Partially supported in ${partiallySupportedClientsString}`
242242
: null}
243-
</Result.Description>
244-
<Result.Metadata>
245-
<span className="mr-2">
246-
{row.result.location.start.line.toString().padStart(2, '0')}:
247-
{row.result.location.start.column.toString().padStart(2, '0')}
248-
</span>
243+
249244
<a
250245
href={row.result.entry.url}
251-
className="underline"
246+
className="underline ml-2 decoration-slate-9 decoration-1 hover:decoration-slate-11 transition-colors hover:text-slate-12"
252247
rel="noreferrer"
253248
target="_blank"
254249
>
255-
See more info
250+
More ↗
256251
</a>
252+
</Result.Description>
253+
<Result.Metadata>
254+
{row.result.location.start.line.toString().padStart(2, '0')}:
255+
{row.result.location.start.column.toString().padStart(2, '0')}
257256
</Result.Metadata>
258257
</Result>
259258
);
@@ -287,9 +286,9 @@ Result.Name = ({
287286
}: React.ComponentProps<typeof Results.Column>) => {
288287
return (
289288
<Results.Column {...props}>
290-
<span className="flex uppercase gap-1 items-center group-data-[status=error]/result:text-red-400 group-data-[status=warning]/result:text-orange-300">
289+
<span className="flex uppercase gap-2 items-center group-data-[status=error]/result:text-red-400 group-data-[status=warning]/result:text-orange-300">
291290
<IconWarning />
292-
{children}
291+
{typeof children === 'string' ? sanitize(children) : children}
293292
</span>
294293
</Results.Column>
295294
);

packages/react-email/src/components/toolbar/results.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const Results = ({
88
return (
99
<table
1010
className={cn(
11-
'group relative w-full border-collapse text-left text-slate-10 text-xs',
11+
'group relative w-full border-collapse text-left text-slate-10 text-sm',
1212
className,
1313
)}
1414
>
@@ -41,7 +41,10 @@ Results.Column = ({
4141
...props
4242
}: React.ComponentProps<'td'>) => {
4343
return (
44-
<td className={cn('py-1 align-bottom font-medium', className)} {...props}>
44+
<td
45+
className={cn('py-1.5 align-bottom font-regular', className)}
46+
{...props}
47+
>
4548
{children}
4649
</td>
4750
);

0 commit comments

Comments
 (0)