Skip to content

Commit 2270f43

Browse files
committed
Redesign tab bars
1 parent 88f8d1c commit 2270f43

File tree

7 files changed

+166
-21
lines changed

7 files changed

+166
-21
lines changed

docusaurus.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import remarkNpm2Yarn from '@docusaurus/remark-plugin-npm2yarn';
1+
import remarkNpm2Yarn from './src/plugins/remark-npm2yarn.mjs';
22
import rehypeCodeblockMeta from './src/plugins/rehype-codeblock-meta.mjs';
33
import rehypeStaticToDynamic from './src/plugins/rehype-static-to-dynamic.mjs';
44
import rehypeVideoAspectRatio from './src/plugins/rehype-video-aspect-ratio.mjs';

src/css/custom.css

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176

177177
--docusaurus-highlighted-code-line-bg: hsla(45, 97%, 72%, 0.3);
178178

179-
--codeblock-background-color: #f6f8fa;
179+
--codeblock-background-color: #fafafa;
180180
}
181181

182182
:root[data-theme='dark'] {
@@ -313,7 +313,7 @@
313313

314314
--docusaurus-highlighted-code-line-bg: hsla(45, 77%, 25%, 0.3);
315315

316-
--codeblock-background-color: #282a35;
316+
--codeblock-background-color: #292c33;
317317
}
318318

319319
:not([data-theme='dark']) body {
@@ -617,51 +617,99 @@ header {
617617
content: '← ';
618618
}
619619

620+
.tabs-container {
621+
--tabs-border-color: var(--ifm-toc-border-color);
622+
}
623+
620624
.tabs {
625+
position: relative;
621626
font-weight: var(--ifm-font-weight-semibold);
627+
gap: 0;
628+
629+
&::before {
630+
content: '';
631+
position: absolute;
632+
bottom: 0;
633+
left: 0;
634+
right: 0;
635+
border-bottom: 1px solid var(--tabs-border-color);
636+
z-index: 0;
637+
}
622638
}
623639

624640
.tabs__item {
625-
border-bottom: 2px solid var(--ifm-toc-border-color);
626-
border-bottom-left-radius: 0;
627-
border-bottom-right-radius: 0;
641+
position: relative;
642+
z-index: 1;
643+
border: 1px solid transparent;
644+
border-bottom-color: transparent;
645+
border-radius: var(--ifm-global-radius) var(--ifm-global-radius) 0 0;
646+
background-color: transparent;
647+
648+
&:hover:not(.tabs__item--active) {
649+
border-bottom-color: transparent;
650+
background-color: var(--ifm-hover-overlay);
651+
}
628652
}
629653

630654
.tabs__item--active,
631655
.tabs__item--active:hover {
632-
border-bottom-color: var(--ifm-tabs-color-active-border);
656+
background-color: var(--ifm-background-surface-color);
657+
border-color: var(--tabs-border-color);
658+
border-bottom-color: var(--ifm-background-surface-color);
659+
color: var(--ifm-tabs-color-active);
633660
}
634661

635662
.tabs-container:has(
636663
> .margin-top--md > [role='tabpanel'] > .theme-code-block:only-child
637664
):not(:has(> .margin-top--md > [role='tabpanel'] > :nth-child(2))) {
638-
background-color: var(--codeblock-background-color);
639-
border-radius: var(--ifm-code-border-radius);
640-
641665
& > .margin-top--md {
642666
margin-top: 0 !important;
643667
}
644668

645669
& > .tabs {
646-
border-width: 1px 1px 0 1px;
647-
border-style: solid;
648-
border-color: var(--ifm-toc-border-color);
649-
border-top-left-radius: var(--ifm-code-border-radius);
650-
border-top-right-radius: var(--ifm-code-border-radius);
670+
background-color: var(--codeblock-background-color);
671+
border: 1px solid var(--ifm-toc-border-color);
672+
border-radius: var(--ifm-code-border-radius) var(--ifm-code-border-radius) 0
673+
0;
674+
675+
&::before {
676+
display: none;
677+
}
651678
}
652679

653680
& > .tabs > .tabs__item {
654-
border-top-right-radius: 0;
681+
border: none;
682+
border-bottom: 2px solid transparent;
683+
border-radius: 0;
655684

656-
&:not(:first-child) {
657-
border-top-left-radius: 0;
685+
&:first-child {
686+
border-top-left-radius: calc(var(--ifm-code-border-radius) - 1px);
658687
}
688+
689+
&:hover:not(.tabs__item--active) {
690+
background-color: rgba(128, 128, 128, 0.1);
691+
}
692+
}
693+
694+
& > .tabs > .tabs__item--active,
695+
& > .tabs > .tabs__item--active:hover {
696+
background-color: transparent;
697+
border-bottom-color: var(--ifm-tabs-color-active-border);
698+
}
699+
700+
& [role='tabpanel'] > .theme-code-block:only-child {
701+
border-top: none;
702+
border-top-left-radius: 0;
703+
border-top-right-radius: 0;
659704
}
660705
}
661706

662-
[role='tabpanel'] > .theme-code-block:only-child {
663-
border-top-left-radius: 0;
664-
border-top-right-radius: 0;
707+
.pm-icon {
708+
width: 16px;
709+
height: 16px;
710+
margin: 0 0.5em 0 0;
711+
vertical-align: -0.125em;
712+
box-shadow: none;
665713
}
666714

667715
.col:has(.table-of-contents) {

src/plugins/remark-npm2yarn.mjs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import npmToYarn from 'npm-to-yarn';
2+
import { Parser } from 'acorn';
3+
import acornJsx from 'acorn-jsx';
4+
5+
const parser = Parser.extend(acornJsx());
6+
7+
function parseExpression(code) {
8+
return parser.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
9+
}
10+
11+
function createLabelWithIcon(pm) {
12+
const value = `<><img className="pm-icon" src="/assets/pm/${pm}.svg" alt="" />${pm}</>`;
13+
14+
return {
15+
type: 'mdxJsxAttributeValueExpression',
16+
value,
17+
data: { estree: parseExpression(value) },
18+
};
19+
}
20+
21+
function createTabItem({ code, node, pm }) {
22+
return {
23+
type: 'mdxJsxFlowElement',
24+
name: 'TabItem',
25+
attributes: [
26+
{ type: 'mdxJsxAttribute', name: 'value', value: pm },
27+
{ type: 'mdxJsxAttribute', name: 'label', value: createLabelWithIcon(pm) },
28+
],
29+
children: [{ type: node.type, lang: node.lang, value: code }],
30+
};
31+
}
32+
33+
function transformNode(node, sync, converters) {
34+
const npmCode = node.value;
35+
36+
return {
37+
type: 'mdxJsxFlowElement',
38+
name: 'Tabs',
39+
attributes: sync
40+
? [{ type: 'mdxJsxAttribute', name: 'groupId', value: 'npm2yarn' }]
41+
: [],
42+
children: [
43+
createTabItem({ code: npmCode, node, pm: 'npm' }),
44+
...converters.map((pm) =>
45+
createTabItem({ code: npmToYarn(npmCode, pm), node, pm })
46+
),
47+
],
48+
};
49+
}
50+
51+
function createImportNode() {
52+
const value =
53+
"import Tabs from '@theme/Tabs'\nimport TabItem from '@theme/TabItem'";
54+
55+
return {
56+
type: 'mdxjsEsm',
57+
value,
58+
data: { estree: parseExpression(value) },
59+
};
60+
}
61+
62+
export default function remarkNpm2Yarn(options = {}) {
63+
const { sync = false, converters = ['yarn', 'pnpm', 'bun'] } = options;
64+
65+
return async (root) => {
66+
const { visit } = await import('unist-util-visit');
67+
68+
let transformed = false;
69+
let alreadyImported = false;
70+
71+
visit(root, (node) => {
72+
if (node.type === 'mdxjsEsm' && node.value.includes('@theme/Tabs')) {
73+
alreadyImported = true;
74+
}
75+
76+
if (Array.isArray(node.children)) {
77+
let i = 0;
78+
while (i < node.children.length) {
79+
const child = node.children[i];
80+
if (child.type === 'code' && child.meta === 'npm2yarn') {
81+
node.children[i] = transformNode(child, sync, converters);
82+
transformed = true;
83+
}
84+
i++;
85+
}
86+
}
87+
});
88+
89+
if (transformed && !alreadyImported) {
90+
root.children.unshift(createImportNode());
91+
}
92+
};
93+
}

static/assets/pm/bun.svg

Lines changed: 1 addition & 0 deletions
Loading

static/assets/pm/npm.svg

Lines changed: 1 addition & 0 deletions
Loading

static/assets/pm/pnpm.svg

Lines changed: 1 addition & 0 deletions
Loading

static/assets/pm/yarn.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)