Cách tạo bài viết nhiều Tabs
Docusaurus tích hợp sẵn một component Tabs rất tiện lợi để tạo giao diện dạng tab mà ta có thể dùng trực tiếp trong các tệp MDX. Ta có thể tùy biến giao diện theo ý thích bằng cách override CSS nếu cần. Dưới đây là các bước cụ thể để tạo một giao diện Tab cho bài viết Docusaurus:
1)Sử dụng Tabs có sẵn của Docusaurus
a) Trong tệp MDX, ta import các component Tabs và TabItem từ theme của Docusaurus như sau:
---
title: Ví dụ Tab Component
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Ví dụ sử dụng Tabs
<Tabs>
<TabItem value="first" label="Tab 1" default>
Nội dung cho Tab 1. Bạn có thể viết Markdown, HTML hoặc nhúng các component React khác ở đây.
</TabItem>
<TabItem value="second" label="Tab 2">
Nội dung cho Tab 2. Ví dụ: danh sách, hình ảnh, hay video.
</TabItem>
<TabItem value="third" label="Tab 3">
Nội dung cho Tab 3. Bạn có thể tùy chỉnh theo ý thích.
</TabItem>
</Tabs>
b) Tạo file CSS tùy chỉnh (ví dụ: src/css/customTabs.css):
/* Ví dụ custom style cho Tabs */
.theme-tabs {
border-bottom: 2px solid #333; /* Đường viền dưới cho các tab */
}
.theme-tabs__item {
color: #333; /* Màu chữ của tab ở Light mode */
font-weight: 600;
padding: 10px 16px;
cursor: pointer;
transition: color 0.3s ease;
}
.theme-tabs__item--active {
color: #007acc; /* Màu chữ khi tab đang active */
border-bottom: 2px solid #007acc;
}
/* Dark mode */
html[data-theme="dark"] .theme-tabs__item {
color: #ccc;
}
html[data-theme="dark"] .theme-tabs__item--active {
color: #66d9ef;
border-bottom-color: #66d9ef;
}
c) Import file CSS vào dự án:
Ta có thể import file CSS này vào file src/css/custom.css (nếu dự án đã có sẵn custom CSS) hoặc thêm import ở tệp src/theme/Layout nếu cần.
Ví dụ, trong src/css/custom.css:
@import './customTabs.css';
Điều này sẽ đảm bảo các style tùy chỉnh được áp dụng cho component Tabs (với selectors dựa theo cấu trúc CSS của theme mặc định của Docusaurus).
d) Kiểm tra kết quả
Chạy dự án bằng lệnh:
npm run start
Mở trang MDX chứa Tabs (ví dụ: docs/tab-example.mdx) trên trình duyệt và kiểm tra giao diện ở cả Light và Dark mode.
2)Tự tạo Component Tabs(Nếu muốn tùy biến sâu hơn)
Nếu muốn tự xây dựng component Tabs (thay vì dùng component có sẵn của Docusaurus), ta có thể tạo một component React riêng. Ví dụ:
a) Tạo file src/components/CustomTabs.js:
import React, { useState } from 'react';
import styles from './CustomTabs.module.scss'; // File SCSS tùy chỉnh
const CustomTabs = ({ tabs }) => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div className={styles.tabsContainer}>
<div className={styles.tabsHeader}>
{tabs.map((tab, index) => (
<button
key={index}
className={`${styles.tabItem} ${activeIndex === index ? styles.active : ''}`}
onClick={() => setActiveIndex(index)}
>
{tab.label}
</button>
))}
</div>
<div className={styles.tabContent}>
{tabs[activeIndex].content}
</div>
</div>
);
};
export default CustomTabs;
b) Tạo file src/components/CustomTabs.module.scss:
.tabsContainer {
border: 1px solid #333;
border-radius: 8px;
overflow: hidden;
}
.tabsHeader {
display: flex;
background-color: #f7f7f7;
border-bottom: 1px solid #333;
}
.tabItem {
flex: 1;
padding: 12px 16px;
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
transition: background-color 0.3s ease;
&:hover {
background-color: #ddd;
}
}
.active {
background-color: #ffffff;
font-weight: bold;
border-bottom: 2px solid #007acc;
}
.tabContent {
padding: 16px;
}
/* Dark mode */
html[data-theme="dark"] {
.tabsHeader {
background-color: #2a2a2a;
border-bottom: 1px solid #444;
}
.tabItem {
color: #ccc;
&:hover {
background-color: #444;
}
}
.active {
background-color: #212121;
color: #ffffff;
border-bottom-color: #66d9ef;
}
.tabContent {
background-color: #212121;
color: #ffffff;
}
}
c) Biên tập file MDX cần post ví dụ "tab-example.mdx"
import CustomTabs from '@site/src/components/CustomTabs';
const tabsData = [
{ label: 'Tab 1', content: <div>Nội dung cho Tab 1</div> },
{ label: 'Tab 2', content: <div>Nội dung cho Tab 2</div> },
{ label: 'Tab 3', content: <div>Nội dung cho Tab 3</div> },
];
<CustomTabs tabs={tabsData} />
d) Nâng cao: Giải pháp tùy chỉnh khi bị báo lỗi
Một nguyên nhân thường gặp gây ra lỗi "Could not parse expression with acorn" trong MDX khi sử dụng JSX inline trong đối tượng là do cú pháp của biểu thức JSX bên trong object literal không được parser MDX chấp nhận nếu không được bao bọc đúng cách.
Để xử lý, ta hãy bao bọc các JSX expression trong các dấu ngoặc đơn. Ví dụ, hãy thay đổi định nghĩa của "tabsData" như sau:
import CustomTabs from '@site/src/components/CustomTabs';
const tabsData = [
{ label: 'Tab 1', content: (<div>Nội dung cho Tab 1</div>) },
{ label: 'Tab 2', content: (<div>Nội dung cho Tab 2</div>) },
{ label: 'Tab 3', content: (<div>Nội dung cho Tab 3</div>) },
];
<CustomTabs tabs={tabsData} />
Nếu sau khi áp dụng cách trên vẫn gặp lỗi, ta có thể thử một số gợi ý sau:
Di chuyển khai báo tabsData ra một file riêng (ví dụ: tabsData.js) và import vào file MDX:
-Trong "tabsData.js": (file này đặt trong folder "scr")
export const tabsData = [
{ label: 'Tab 1', content: (<div>Nội dung cho Tab 1</div>) },
{ label: 'Tab 2', content: (<div>Nội dung cho Tab 2</div>) },
{ label: 'Tab 3', content: (<div>Nội dung cho Tab 3</div>) },
];
-Trong file MDX:
import CustomTabs from '@site/src/components/CustomTabs';
import { tabsData } from '@site/src/tabsData';
<CustomTabs tabs={tabsData} />
Điều này giúp tách riêng phần code JavaScript và cho MDX xử lý JSX dễ dàng hơn.
e) Thực tế: Áp dụng cách vừa đề cập đem lại thành quả tốt đẹp, với mã nguồn thực tế:
-"src/components/CustomTabs.module.scss":
.tabsContainer {
border: 1px solid #333;
border-radius: 8px;
overflow: hidden;
}
.tabsHeader {
display: flex;
background-color: #f7f7f7;
border-bottom: 1px solid #333;
}
.tabItem {
flex: 1;
padding: 12px 16px;
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
transition: background-color 0.3s ease;
&:hover {
background-color: #ddd;
}
}
.active {
background-color: #ffffff;
font-weight: bold;
border-bottom: 2px solid #007acc;
}
.tabContent {
padding: 16px;
}
/* Dark mode */
html[data-theme="dark"] {
.tabsHeader {
background-color: #2a2a2a;
border-bottom: 1px solid #444;
}
.tabItem {
color: #ccc;
&:hover {
background-color: #444;
}
}
.active {
background-color: #212121;
color: #ffffff;
border-bottom-color: #66d9ef;
}
.tabContent {
background-color: #212121;
color: #ffffff;
}
}
-"src/components/CustomTabs.js":
// src/components/CustomTabs.js
import React, { useState } from 'react';
import styles from './CustomTabs.module.scss'; // Giả sử bạn có file CSS module để tùy chỉnh style
const CustomTabs = ({ tabs }) => {
const [activeIndex, setActiveIndex] = useState(0);
const activeTab = tabs[activeIndex];
return (
<div className={styles.tabsContainer}>
<div className={styles.tabsHeader}>
{tabs.map((tab, index) => (
<button
key={index}
className={`${styles.tabItem} ${activeIndex === index ? styles.active : ''}`}
onClick={() => setActiveIndex(index)}
// Áp dụng màu nền cho nút tab nếu nó đang active và có bgColor
style={activeIndex === index && tab.bgColor ? { backgroundColor: tab.bgColor } : {}}
>
{tab.label}
</button>
))}
</div>
<div
className={styles.tabContent}
// Áp dụng màu nền cho vùng nội dung của tab active nếu có bgColor
style={activeTab.bgColor ? { backgroundColor: activeTab.bgColor } : {}}
>
{activeTab.content}
</div>
</div>
);
};
export default CustomTabs;
-"scr/tabsData.js":
export const tabsData = [
{ label: 'Tab 1', content: (<div>Nội dung cho Tab 1</div>) },
{ label: 'Tab 2', content: (<div>Nội dung cho Tab 2</div>) },
{ label: 'Tab 3', content: (<div>Nội dung cho Tab 3</div>) },
];
-"tab-example.mdx":
import CustomTabs from '@site/src/components/CustomTabs';
import { tabsData } from '@site/src/tabsData';
<CustomTabs tabs={tabsData} />
g) Lợi ích:
Cách tổ chức mã nguồn theo mô‑đun, giúp ta dễ dàng tái sử dụng và quản lý dữ liệu ở nhiều nơi khác nhau, dễ theo dõi và cập nhật
File "tab-example.mdx" chỉ chứa code để import dữ liệu từ "tabsData.js" và render component "CustomTabs". Khi chỉnh sửa chỉ cần sửa file "tabsData.js".
h) Tổ chức các bài post khác nhau cùng sử dụng layout nhiều Tab như mẫu này
Có 2 cách để định nghĩa nội dung cho các tab của mỗi bài post
-Cách inline trong file MDX:
Trong mỗi file MDX, bạn có thể định nghĩa trực tiếp dữ liệu cho các tab (ví dụ, bằng cách tạo một biến chứa mảng đối tượng cho dữ liệu tab) và sử dụng component CustomTabs để hiển thị.
---
title: "Bài Post 1 với Tab Layout"
date: 2025-02-05
---
import CustomTabs from '@site/src/components/CustomTabs';
{/*
Định nghĩa dữ liệu cho các tab ngay trong bài post.
*/}
const tabsData = [
{ label: 'Tab 1', content: (<div>Nội dung Tab 1 của bài Post 1</div>) },
{ label: 'Tab 2', content: (<div>Nội dung Tab 2 của bài Post 1</div>) },
{ label: 'Tab 3', content: (<div>Nội dung Tab 3 của bài Post 1</div>) },
];
<CustomTabs tabs={tabsData} />
-Cách import từ module riêng:
Nếu ta muốn tách riêng dữ liệu, bạn có thể tạo các file module cho từng bài post (ví dụ, post1TabsData.js, post2TabsData.js,…) chứa nội dung của các tab.
Ví dụ, tạo file src/data/post1TabsData.js:
export const tabsData = [
{ label: 'Tab 1', content: (<div>Nội dung Tab 1 của bài Post 1</div>) },
{ label: 'Tab 2', content: (<div>Nội dung Tab 2 của bài Post 1</div>) },
{ label: 'Tab 3', content: (<div>Nội dung Tab 3 của bài Post 1</div>) },
];
Sau đó, trong file MDX của bài post 1 (2025-02-05-post-1.mdx), ta import:
---
title: "Bài Post 1 với Tab Layout"
date: 2025-02-05
---
import CustomTabs from '@site/src/components/CustomTabs';
import { tabsData } from '@site/src/data/post1TabsData';
<CustomTabs tabs={tabsData} />
Dữ liệu cho các tab được lưu trữ riêng cho mỗi bài, cho dù ta định nghĩa inline hay trong một file riêng. Như vậy, mỗi bài post sẽ có nội dung tab khác nhau và được phân biệt bởi file MDX của nó và nếu cần sử dụng module riêng để chứa dữ liệu tab, ta nên đặt tên file sao cho phản ánh nội dung hoặc bài post đó (ví dụ: post1TabsData.js, post2TabsData.js, v.v.) để dễ quản lý và phân biệt.
Bài viết đã tạo ra dùng hướng dẫn này:
LION AND RABBIT