react-email 1.7.2 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/_preview/components.js +1 -74
  2. package/dist/_preview/pages.js +1 -19
  3. package/dist/_preview/root.js +1 -26
  4. package/dist/_preview/styles.js +1 -6
  5. package/dist/_preview/utils.js +1 -22
  6. package/dist/package.json +54 -0
  7. package/dist/source/_preview/components.d.ts +4 -0
  8. package/dist/source/_preview/components.js +77 -0
  9. package/dist/source/_preview/pages.d.ts +9 -0
  10. package/dist/source/_preview/pages.js +22 -0
  11. package/dist/source/_preview/root.d.ts +4 -0
  12. package/dist/source/_preview/root.js +29 -0
  13. package/dist/source/_preview/styles.d.ts +4 -0
  14. package/dist/source/_preview/styles.js +9 -0
  15. package/dist/source/_preview/utils.d.ts +4 -0
  16. package/dist/source/_preview/utils.js +25 -0
  17. package/dist/source/commands/dev.d.ts +1 -0
  18. package/dist/source/commands/dev.js +186 -0
  19. package/dist/source/commands/export.d.ts +2 -0
  20. package/dist/source/commands/export.js +80 -0
  21. package/dist/source/index.d.ts +2 -0
  22. package/dist/source/index.js +27 -0
  23. package/dist/source/utils/check-directory-exist.d.ts +1 -0
  24. package/dist/source/utils/check-directory-exist.js +9 -0
  25. package/dist/source/utils/check-empty-directory.d.ts +1 -0
  26. package/dist/source/utils/check-empty-directory.js +12 -0
  27. package/dist/source/utils/check-is-up-to-date.d.ts +2 -0
  28. package/dist/source/utils/check-is-up-to-date.js +26 -0
  29. package/dist/source/utils/constants.d.ts +10 -0
  30. package/dist/source/utils/constants.js +22 -0
  31. package/dist/source/utils/create-directory.d.ts +1 -0
  32. package/dist/source/utils/create-directory.js +9 -0
  33. package/dist/source/utils/index.d.ts +6 -0
  34. package/dist/source/utils/index.js +22 -0
  35. package/dist/source/utils/watcher.d.ts +3 -0
  36. package/dist/source/utils/watcher.js +35 -0
  37. package/package.json +1 -1
  38. package/preview/package.json +2 -1
  39. package/preview/src/components/code-container.tsx +32 -11
  40. package/preview/src/components/icon-button.tsx +1 -1
  41. package/preview/src/components/sidebar.tsx +70 -48
  42. package/preview/src/components/topbar.tsx +64 -39
  43. package/preview/src/pages/preview/[slug].tsx +2 -7
  44. package/source/_preview/components.ts +4 -4
  45. package/source/_preview/pages.ts +1 -1
  46. package/source/_preview/root.ts +1 -1
  47. package/source/index.ts +2 -1
  48. package/source/utils/constants.ts +1 -1
  49. package/source/utils/watcher.ts +10 -10
  50. package/tsconfig.json +1 -1
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.exportTemplates = void 0;
30
+ const glob_1 = require("glob");
31
+ const esbuild_1 = __importDefault(require("esbuild"));
32
+ const tree_node_cli_1 = __importDefault(require("tree-node-cli"));
33
+ const ora_1 = __importDefault(require("ora"));
34
+ const log_symbols_1 = __importDefault(require("log-symbols"));
35
+ const render_1 = require("@react-email/render");
36
+ const fs_1 = require("fs");
37
+ const cpy_1 = __importDefault(require("cpy"));
38
+ const normalize_path_1 = __importDefault(require("normalize-path"));
39
+ const utils_1 = require("../utils");
40
+ /*
41
+ This first builds all the templates using esbuild and then puts the output in the `.js`
42
+ files. Then these `.js` files are imported dynamically and rendered to `.html` files
43
+ using the `render` function.
44
+ */
45
+ const exportTemplates = async (outDir, options) => {
46
+ const spinner = (0, ora_1.default)('Preparing files...\n').start();
47
+ const allTemplates = glob_1.glob.sync((0, normalize_path_1.default)(`${utils_1.CLIENT_EMAILS_PATH}/*.{tsx,jsx}`));
48
+ esbuild_1.default.buildSync({
49
+ bundle: true,
50
+ entryPoints: allTemplates,
51
+ platform: 'node',
52
+ write: true,
53
+ outdir: outDir,
54
+ });
55
+ const allBuiltTemplates = glob_1.glob.sync((0, normalize_path_1.default)(`${outDir}/*.js`), {
56
+ absolute: true,
57
+ });
58
+ for (const template of allBuiltTemplates) {
59
+ const component = await Promise.resolve().then(() => __importStar(require(template)));
60
+ const rendered = (0, render_1.render)(component.default(), options);
61
+ const htmlPath = template.replace('.js', options.plainText ? '.txt' : '.html');
62
+ (0, fs_1.writeFileSync)(htmlPath, rendered);
63
+ (0, fs_1.unlinkSync)(template);
64
+ }
65
+ const hasStaticDirectory = (0, utils_1.checkDirectoryExist)(`${utils_1.CLIENT_EMAILS_PATH}/static`);
66
+ if (hasStaticDirectory) {
67
+ await (0, cpy_1.default)(`${utils_1.CLIENT_EMAILS_PATH}/static`, `${outDir}/static`);
68
+ }
69
+ const fileTree = (0, tree_node_cli_1.default)(outDir, {
70
+ allFiles: true,
71
+ maxDepth: 4,
72
+ });
73
+ console.log(fileTree);
74
+ spinner.stopAndPersist({
75
+ symbol: log_symbols_1.default.success,
76
+ text: 'Successfully exported emails',
77
+ });
78
+ process.exit();
79
+ };
80
+ exports.exportTemplates = exportTemplates;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const extra_typings_1 = require("@commander-js/extra-typings");
8
+ const constants_1 = require("./utils/constants");
9
+ const dev_1 = require("./commands/dev");
10
+ const export_1 = require("./commands/export");
11
+ const package_json_1 = __importDefault(require("../package.json"));
12
+ extra_typings_1.program
13
+ .name(constants_1.PACKAGE_NAME)
14
+ .description('A live preview of your emails right in your browser')
15
+ .version(package_json_1.default.version);
16
+ extra_typings_1.program
17
+ .command('dev')
18
+ .description('Starts the application in development mode')
19
+ .action(dev_1.dev);
20
+ extra_typings_1.program
21
+ .command('export')
22
+ .description('Build the templates to the `out` directory')
23
+ .option('--outDir <path>', 'Output directory', 'out')
24
+ .option('-p, --pretty', 'Pretty print the output', false)
25
+ .option('-t, --plainText', 'Set output format as plain Text', false)
26
+ .action(({ outDir, pretty, plainText }) => (0, export_1.exportTemplates)(outDir, { pretty, plainText }));
27
+ extra_typings_1.program.parse();
@@ -0,0 +1 @@
1
+ export declare const checkDirectoryExist: (path: string) => boolean;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkDirectoryExist = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const checkDirectoryExist = (path) => fs_1.default.existsSync(path);
9
+ exports.checkDirectoryExist = checkDirectoryExist;
@@ -0,0 +1 @@
1
+ export declare const checkEmptyDirectory: (path: string) => Promise<boolean>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkEmptyDirectory = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const checkEmptyDirectory = async (path) => {
9
+ const files = await fs_1.default.promises.readdir(path);
10
+ return files.length === 0;
11
+ };
12
+ exports.checkEmptyDirectory = checkEmptyDirectory;
@@ -0,0 +1,2 @@
1
+ export declare const checkPackageIsUpToDate: () => Promise<boolean>;
2
+ export declare const getPreviewPkg: () => any;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPreviewPkg = exports.checkPackageIsUpToDate = void 0;
7
+ const root_1 = require("../_preview/root");
8
+ const constants_1 = require("./constants");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const checkPackageIsUpToDate = async () => {
12
+ try {
13
+ const reactEmailPkg = await fs_1.default.promises.readFile(path_1.default.join(constants_1.REACT_EMAIL_ROOT, 'package.json'), { encoding: 'utf8' });
14
+ const isUpToDate = JSON.parse(reactEmailPkg).version === (0, exports.getPreviewPkg)().version;
15
+ return isUpToDate;
16
+ }
17
+ catch (error) {
18
+ throw new Error('Error checking if app is up-to-date');
19
+ }
20
+ };
21
+ exports.checkPackageIsUpToDate = checkPackageIsUpToDate;
22
+ const getPreviewPkg = () => {
23
+ const [previewPkg] = root_1.root.filter((pkg) => pkg.title === 'package.json');
24
+ return JSON.parse(previewPkg?.content || '');
25
+ };
26
+ exports.getPreviewPkg = getPreviewPkg;
@@ -0,0 +1,10 @@
1
+ export declare const DEFAULT_EMAILS_DIRECTORY = "emails";
2
+ export declare const PACKAGE_NAME = "react-email";
3
+ export declare const CURRENT_PATH: string;
4
+ export declare const CLIENT_PACKAGE_JSON: string;
5
+ export declare const REACT_EMAIL_ROOT: string;
6
+ export declare const SRC_PATH: string;
7
+ export declare const PUBLIC_PATH: string;
8
+ export declare const EVENT_FILE_DELETED = "unlink";
9
+ export declare const CLIENT_EMAILS_PATH: string;
10
+ export declare const PACKAGE_EMAILS_PATH: string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PACKAGE_EMAILS_PATH = exports.CLIENT_EMAILS_PATH = exports.EVENT_FILE_DELETED = exports.PUBLIC_PATH = exports.SRC_PATH = exports.REACT_EMAIL_ROOT = exports.CLIENT_PACKAGE_JSON = exports.CURRENT_PATH = exports.PACKAGE_NAME = exports.DEFAULT_EMAILS_DIRECTORY = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ // Package variables
9
+ exports.DEFAULT_EMAILS_DIRECTORY = 'emails';
10
+ exports.PACKAGE_NAME = 'react-email';
11
+ // Default paths
12
+ exports.CURRENT_PATH = process.cwd();
13
+ // Client paths
14
+ exports.CLIENT_PACKAGE_JSON = path_1.default.join(exports.CURRENT_PATH, 'package.json');
15
+ // React Email paths
16
+ exports.REACT_EMAIL_ROOT = path_1.default.join(exports.CURRENT_PATH, '.react-email');
17
+ exports.SRC_PATH = path_1.default.join(exports.REACT_EMAIL_ROOT, 'src');
18
+ exports.PUBLIC_PATH = path_1.default.join(exports.REACT_EMAIL_ROOT, 'public');
19
+ // Events
20
+ exports.EVENT_FILE_DELETED = 'unlink';
21
+ exports.CLIENT_EMAILS_PATH = path_1.default.join(exports.CURRENT_PATH, exports.DEFAULT_EMAILS_DIRECTORY);
22
+ exports.PACKAGE_EMAILS_PATH = path_1.default.join(exports.REACT_EMAIL_ROOT, exports.DEFAULT_EMAILS_DIRECTORY);
@@ -0,0 +1 @@
1
+ export declare const createDirectory: (directory: string) => Promise<string | undefined>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDirectory = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const createDirectory = async (directory) => fs_1.default.promises.mkdir(directory, { recursive: true });
9
+ exports.createDirectory = createDirectory;
@@ -0,0 +1,6 @@
1
+ export * from './check-directory-exist';
2
+ export * from './check-empty-directory';
3
+ export * from './check-is-up-to-date';
4
+ export * from './constants';
5
+ export * from './create-directory';
6
+ export * from './watcher';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./check-directory-exist"), exports);
18
+ __exportStar(require("./check-empty-directory"), exports);
19
+ __exportStar(require("./check-is-up-to-date"), exports);
20
+ __exportStar(require("./constants"), exports);
21
+ __exportStar(require("./create-directory"), exports);
22
+ __exportStar(require("./watcher"), exports);
@@ -0,0 +1,3 @@
1
+ import chokidar from 'chokidar';
2
+ export declare const watcherInstance: chokidar.FSWatcher;
3
+ export declare const watcher: () => chokidar.FSWatcher;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.watcher = exports.watcherInstance = void 0;
7
+ const chokidar_1 = __importDefault(require("chokidar"));
8
+ const constants_1 = require("./constants");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const cpy_1 = __importDefault(require("cpy"));
12
+ exports.watcherInstance = chokidar_1.default.watch(constants_1.CLIENT_EMAILS_PATH, {
13
+ ignoreInitial: true,
14
+ cwd: constants_1.CURRENT_PATH,
15
+ ignored: /(^|[\/\\])\../,
16
+ });
17
+ const watcher = () => exports.watcherInstance.on('all', async (event, filename) => {
18
+ if (event === constants_1.EVENT_FILE_DELETED) {
19
+ const file = filename.split(path_1.default.sep);
20
+ if (file[1] === 'static' && file[2]) {
21
+ await fs_1.default.promises.rm(path_1.default.join(constants_1.REACT_EMAIL_ROOT, 'public', 'static', file[2]));
22
+ return;
23
+ }
24
+ await fs_1.default.promises.rm(path_1.default.join(constants_1.REACT_EMAIL_ROOT, filename));
25
+ }
26
+ else {
27
+ const file = filename.split(path_1.default.sep);
28
+ if (file[1] === 'static' && file[2]) {
29
+ await (0, cpy_1.default)(`${constants_1.CLIENT_EMAILS_PATH}/static/${file[2]}`, `${constants_1.REACT_EMAIL_ROOT}/public/static`);
30
+ return;
31
+ }
32
+ await (0, cpy_1.default)(path_1.default.join(constants_1.CURRENT_PATH, filename), path_1.default.join(constants_1.REACT_EMAIL_ROOT, file.slice(0, -1).join(path_1.default.sep)));
33
+ }
34
+ });
35
+ exports.watcher = watcher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/index.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email-preview",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "The React Email preview application",
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -24,6 +24,7 @@
24
24
  "@radix-ui/react-tooltip": "1.0.2",
25
25
  "@react-email/render": "0.0.6",
26
26
  "classnames": "2.3.2",
27
+ "framer-motion": "8.4.6",
27
28
  "next": "13.0.4",
28
29
  "prism-react-renderer": "1.3.5",
29
30
  "react": "18.2.0",
@@ -7,6 +7,7 @@ import { copyTextToClipboard } from '../utils';
7
7
  import languageMap from '../utils/language-map';
8
8
  import { Tooltip } from './tooltip';
9
9
  import { Code } from './code';
10
+ import { AnimateSharedLayout, motion } from 'framer-motion';
10
11
  import * as React from 'react';
11
12
 
12
13
  interface CodeContainerProps {
@@ -21,6 +22,7 @@ interface MarkupProps {
21
22
  export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
22
23
  markups,
23
24
  }) => {
25
+ const [hovered, setHovered] = React.useState('');
24
26
  const [isCopied, setIsCopied] = React.useState(false);
25
27
  const [activeTab, setActiveTab] = React.useState(markups[0].language);
26
28
  let file = null;
@@ -32,7 +34,11 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
32
34
  url = URL.createObjectURL(file);
33
35
 
34
36
  return (
35
- <a href={url} download={file.name}>
37
+ <a
38
+ href={url}
39
+ download={file.name}
40
+ className="text-slate-11 transition ease-in-out duration-200 hover:text-slate-12"
41
+ >
36
42
  <IconDownload />
37
43
  </a>
38
44
  );
@@ -72,19 +78,34 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
72
78
  }}
73
79
  >
74
80
  <div className="h-9 border-b border-slate-6">
75
- <div className="py-[10px] px-4 text-xs flex gap-8">
76
- {markups.map(({ language }) => {
77
- return (
78
- <div key={language}>
79
- <button
80
- className={`${activeTab !== language && 'opacity-25'}`}
81
+ <div className="flex">
82
+ <AnimateSharedLayout>
83
+ {markups.map(({ language }) => {
84
+ const isHovered = hovered === language;
85
+ return (
86
+ <motion.button
87
+ className={`relative py-[8px] px-4 text-sm font-medium font-sans transition ease-in-out duration-200 ${
88
+ activeTab !== language ? 'text-slate-11' : 'text-slate-12'
89
+ }`}
81
90
  onClick={() => setActiveTab(language)}
91
+ onHoverStart={() => setHovered(language)}
92
+ onHoverEnd={() => setHovered('')}
93
+ key={language}
82
94
  >
95
+ {isHovered && (
96
+ <motion.span
97
+ layoutId="nav"
98
+ className="absolute left-0 right-0 top-0 bottom-0 bg-slate-4"
99
+ initial={{ opacity: 0 }}
100
+ animate={{ opacity: 1 }}
101
+ exit={{ opacity: 0 }}
102
+ />
103
+ )}
83
104
  {languageMap[language]}
84
- </button>
85
- </div>
86
- );
87
- })}
105
+ </motion.button>
106
+ );
107
+ })}
108
+ </AnimateSharedLayout>
88
109
  </div>
89
110
  <Tooltip>
90
111
  <Tooltip.Trigger className="absolute top-2 right-2 hidden md:block">
@@ -12,7 +12,7 @@ export const IconButton = React.forwardRef<
12
12
  {...props}
13
13
  ref={forwardedRef}
14
14
  className={classnames(
15
- 'rounded text-gray-11 focus:text-gray-12 ease-in-out transition duration-200 focus:outline-none focus:ring-2 focus:ring-gray-8 hover:text-gray-12',
15
+ 'rounded text-slate-11 focus:text-slate-12 ease-in-out transition duration-200 focus:outline-none focus:ring-2 focus:ring-gray-8 hover:text-slate-12',
16
16
  className,
17
17
  )}
18
18
  >
@@ -5,6 +5,7 @@ import Link from 'next/link';
5
5
  import { Heading } from './heading';
6
6
  import { useRouter } from 'next/router';
7
7
  import * as Collapsible from '@radix-ui/react-collapsible';
8
+ import { AnimateSharedLayout, motion } from 'framer-motion';
8
9
 
9
10
  type SidebarElement = React.ElementRef<'aside'>;
10
11
  type RootProps = React.ComponentPropsWithoutRef<'aside'>;
@@ -15,6 +16,7 @@ interface SidebarProps extends RootProps {
15
16
 
16
17
  export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
17
18
  ({ className, navItems, ...props }, forwardedRef) => {
19
+ const [hovered, setHovered] = React.useState('');
18
20
  const { query } = useRouter();
19
21
 
20
22
  return (
@@ -58,13 +60,18 @@ export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
58
60
  />
59
61
  </svg>
60
62
 
61
- <div className="flex items-center">
62
- <Heading as="h3" color="white" size="2" weight="medium">
63
+ <div className="flex items-center text-slate-11 transition ease-in-out duration-200 hover:text-slate-12">
64
+ <Heading
65
+ as="h3"
66
+ color="gray"
67
+ size="2"
68
+ weight="medium"
69
+ className="transition ease-in-out duration-200 hover:text-slate-12"
70
+ >
63
71
  All emails
64
72
  </Heading>
65
73
  {navItems && navItems.length > 0 && (
66
74
  <svg
67
- className="text-slate-11"
68
75
  width="24"
69
76
  height="24"
70
77
  viewBox="0 0 24 24"
@@ -84,51 +91,66 @@ export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
84
91
  <Collapsible.Content className="relative mt-3">
85
92
  <div className="absolute left-2.5 w-px h-full bg-slate-6" />
86
93
 
87
- <div className="py-2 flex flex-col gap-1.5 truncate">
88
- {navItems &&
89
- navItems.map((item) => (
90
- <Link key={item} href={`/preview/${item}`}>
91
- <span
92
- className={classnames(
93
- 'text-[14px] flex items-center font-medium gap-2 h-8 w-full pl-4 rounded-md text-slate-11',
94
- {
95
- 'bg-cyan-3 text-cyan-11': query.slug === item,
96
- 'hover:text-slate-12': query.slug !== item,
97
- },
98
- )}
99
- >
100
- {query.slug === item && (
101
- <div className="h-5 bg-cyan-11 w-px absolute left-2.5" />
102
- )}
103
- <svg
104
- className="flex-shrink-0"
105
- width="24"
106
- height="24"
107
- viewBox="0 0 24 24"
108
- fill="none"
109
- xmlns="http://www.w3.org/2000/svg"
110
- >
111
- <path
112
- d="M7.75 19.25H16.25C17.3546 19.25 18.25 18.3546 18.25 17.25V9L14 4.75H7.75C6.64543 4.75 5.75 5.64543 5.75 6.75V17.25C5.75 18.3546 6.64543 19.25 7.75 19.25Z"
113
- stroke="currentColor"
114
- strokeOpacity="0.927"
115
- strokeWidth="1.5"
116
- strokeLinecap="round"
117
- strokeLinejoin="round"
118
- />
119
- <path
120
- d="M18 9.25H13.75V5"
121
- stroke="currentColor"
122
- strokeOpacity="0.927"
123
- strokeWidth="1.5"
124
- strokeLinecap="round"
125
- strokeLinejoin="round"
126
- />
127
- </svg>
128
- {item}
129
- </span>
130
- </Link>
131
- ))}
94
+ <div className="py-2 flex flex-col truncate">
95
+ <AnimateSharedLayout>
96
+ {navItems &&
97
+ navItems.map((item) => {
98
+ const isHovered = hovered === item;
99
+ return (
100
+ <Link key={item} href={`/preview/${item}`}>
101
+ <motion.span
102
+ className={classnames(
103
+ 'text-[14px] flex items-center font-medium gap-2 w-full pl-4 h-8 rounded-md text-slate-11 relative block transition ease-in-out duration-200',
104
+ {
105
+ 'bg-cyan-3 text-cyan-11': query.slug === item,
106
+ 'hover:text-slate-12': query.slug !== item,
107
+ },
108
+ )}
109
+ onHoverStart={() => setHovered(item)}
110
+ onHoverEnd={() => setHovered('')}
111
+ >
112
+ {isHovered && (
113
+ <motion.span
114
+ layoutId="sidebar"
115
+ className="absolute left-0 right-0 top-0 bottom-0 rounded-md bg-slate-5"
116
+ initial={{ opacity: 0 }}
117
+ animate={{ opacity: 1 }}
118
+ exit={{ opacity: 0 }}
119
+ >
120
+ <div className="bg-cyan-11 w-px absolute top-1 left-2.5 h-6" />
121
+ </motion.span>
122
+ )}
123
+ <svg
124
+ className="flex-shrink-0"
125
+ width="24"
126
+ height="24"
127
+ viewBox="0 0 24 24"
128
+ fill="none"
129
+ xmlns="http://www.w3.org/2000/svg"
130
+ >
131
+ <path
132
+ d="M7.75 19.25H16.25C17.3546 19.25 18.25 18.3546 18.25 17.25V9L14 4.75H7.75C6.64543 4.75 5.75 5.64543 5.75 6.75V17.25C5.75 18.3546 6.64543 19.25 7.75 19.25Z"
133
+ stroke="currentColor"
134
+ strokeOpacity="0.927"
135
+ strokeWidth="1.5"
136
+ strokeLinecap="round"
137
+ strokeLinejoin="round"
138
+ />
139
+ <path
140
+ d="M18 9.25H13.75V5"
141
+ stroke="currentColor"
142
+ strokeOpacity="0.927"
143
+ strokeWidth="1.5"
144
+ strokeLinecap="round"
145
+ strokeLinejoin="round"
146
+ />
147
+ </svg>
148
+ {item}
149
+ </motion.span>
150
+ </Link>
151
+ );
152
+ })}
153
+ </AnimateSharedLayout>
132
154
  </div>
133
155
  </Collapsible.Content>
134
156
  )}