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.
- package/dist/_preview/components.js +1 -74
- package/dist/_preview/pages.js +1 -19
- package/dist/_preview/root.js +1 -26
- package/dist/_preview/styles.js +1 -6
- package/dist/_preview/utils.js +1 -22
- package/dist/package.json +54 -0
- package/dist/source/_preview/components.d.ts +4 -0
- package/dist/source/_preview/components.js +77 -0
- package/dist/source/_preview/pages.d.ts +9 -0
- package/dist/source/_preview/pages.js +22 -0
- package/dist/source/_preview/root.d.ts +4 -0
- package/dist/source/_preview/root.js +29 -0
- package/dist/source/_preview/styles.d.ts +4 -0
- package/dist/source/_preview/styles.js +9 -0
- package/dist/source/_preview/utils.d.ts +4 -0
- package/dist/source/_preview/utils.js +25 -0
- package/dist/source/commands/dev.d.ts +1 -0
- package/dist/source/commands/dev.js +186 -0
- package/dist/source/commands/export.d.ts +2 -0
- package/dist/source/commands/export.js +80 -0
- package/dist/source/index.d.ts +2 -0
- package/dist/source/index.js +27 -0
- package/dist/source/utils/check-directory-exist.d.ts +1 -0
- package/dist/source/utils/check-directory-exist.js +9 -0
- package/dist/source/utils/check-empty-directory.d.ts +1 -0
- package/dist/source/utils/check-empty-directory.js +12 -0
- package/dist/source/utils/check-is-up-to-date.d.ts +2 -0
- package/dist/source/utils/check-is-up-to-date.js +26 -0
- package/dist/source/utils/constants.d.ts +10 -0
- package/dist/source/utils/constants.js +22 -0
- package/dist/source/utils/create-directory.d.ts +1 -0
- package/dist/source/utils/create-directory.js +9 -0
- package/dist/source/utils/index.d.ts +6 -0
- package/dist/source/utils/index.js +22 -0
- package/dist/source/utils/watcher.d.ts +3 -0
- package/dist/source/utils/watcher.js +35 -0
- package/package.json +1 -1
- package/preview/package.json +2 -1
- package/preview/src/components/code-container.tsx +32 -11
- package/preview/src/components/icon-button.tsx +1 -1
- package/preview/src/components/sidebar.tsx +70 -48
- package/preview/src/components/topbar.tsx +64 -39
- package/preview/src/pages/preview/[slug].tsx +2 -7
- package/source/_preview/components.ts +4 -4
- package/source/_preview/pages.ts +1 -1
- package/source/_preview/root.ts +1 -1
- package/source/index.ts +2 -1
- package/source/utils/constants.ts +1 -1
- package/source/utils/watcher.ts +10 -10
- 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,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,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,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,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
package/preview/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email-preview",
|
|
3
|
-
"version": "0.0.
|
|
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
|
|
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="
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
|
88
|
-
|
|
89
|
-
navItems
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
)}
|