This commit is contained in:
oneao committed 2026-01-07 17:31:49 +08:00
1 parent 17e07df91a
commit 3ef263f5cc
47 files changed
+1169 -1041

No files matched your search

Whitespace-only changes.
@@ -0,0 +1 @@
1
@@ -1,5 +1,5 @@
{
"label": "Tutorial - Extras",
"label": "编程",
"position": 3,
"link": {
"type": "generated-index"
@@ -0,0 +1,9 @@
[React Native](https://reactnative.dev/) 是一个开源框架,用于构建跨平台的移动应用。它允许开发者使用 React 来构建原生的 iOS 和 Android 应用。
主要特点:
- 跨平台开发:通过一次编写代码,React Native 可以在 iOS 和 Android 上运行,大大减少开发时间和成本。
- 原生性能:React Native 使用原生组件(如按钮、文本框等),提供接近原生应用的性能和用户体验。
- 热重载:开发者可以实时查看代码修改的效果,而无需重新加载整个应用。
- 组件化开发:通过 React 的组件化理念,开发者可以更高效地构建和维护应用界面。
- 丰富的生态系统:React Native 拥有大量的第三方库和工具,帮助开发者快速实现功能。
@@ -0,0 +1,8 @@
[Expo](https://docs.expo.dev/) 是一个基于 React Native 的框架,旨在简化移动应用开发。它提供了一套工具和服务,让开发者可以更快速地构建、测试和发布 React Native 应用。
主要特点:
- 快速上手:无需复杂配置,直接在手机上查看代码效果。
- 内置功能:相机、位置服务、推送通知等常用功能,免去手动集成。
- 跨平台支持:同一套代码可在 iOS 和 Android 上运行。
- 热重载:修改代码后,应用会即时更新,提升开发效率。
@@ -0,0 +1,22 @@
创建项目命令:
```bash
pnpm create expo-app@latest --template blank-typescript
```
[官网模板](https://docs.expo.dev/more/create-expo/) 如下:
| 模板名称 | 描述 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| [`default`](https://github.com/expo/expo/tree/main/templates/expo-template-default) | 默认模板,用于构建多页面应用。包含推荐工具,如 **Expo CLI**、**Expo Router** 路由库,并启用 **TypeScript** 配置。适合大多数应用。 |
| [`blank`](https://github.com/expo/expo/tree/main/templates/expo-template-blank) | 最简模板,仅安装最基本依赖,不包含导航配置。 |
| [`blank-typescript`](https://github.com/expo/expo/tree/main/templates/expo-template-blank-typescript) | 空白模板,但启用了 **TypeScript** 支持。 |
| [`tabs`](https://github.com/expo/expo/tree/main/templates/expo-template-tabs) | 启用基于文件的路由系统(Expo Router),并默认使用 **TypeScript**,适合多标签页结构的应用。 |
| [`bare-minimum`](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum) | 带有原生目录(`android` 与 `ios`)的空白模板,创建时会自动运行 [`npx expo prebuild`](https://docs.expo.dev/workflow/prebuild)。适合需要直接修改原生代码的项目。 |
**启动项目:**
```bash
npx expo start
```
@@ -0,0 +1,166 @@
**生成基础文件**,后面要用到:
```bash
npx expo customize babel.config.js
npx expo customize metro.config.js
```
## 配置eslint格式化
可以使用快捷命令(推荐):
```bash
pnpm dlx @antfu/eslint-config@latest
```
安装:
```bash
pnpm i -D eslint @antfu/eslint-config
```
新建 `eslint.config.mjs` 文件:
```js
import antfu from '@antfu/eslint-config'
export default antfu({
stylistic: {
indent: 2, // 缩进为 2 个空格(可选 4 或 'tab')
quotes: 'single', // 使用单引号(可选 'double')
},
rules: {
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': 'warn',
'@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告
'no-console': 'off', // 关闭 console 报错
},
})
```
配置 vscode 模板,修改 `.vscode/settings.json` :
> 注意安装 eslint 插件
```json
{
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
}
```
## 配置深度链接
作用:通过链接就可以跳转到该app。
在 `app.json` 文件下进行配置:
```json
{
"expo": {
"scheme": "oneao"
}
}
```
然后就可以通过这个配置 从别的应用或者浏览器跳转到你的app:
```
oneao://courses/react-native
```
## 定义 src 路径别名 @
> 参考:https://docs.expo.dev/guides/typescript/
修改 `tsconfig.json` 文件:
```json
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"strict": true
}
}
```
安装 `module-resolver` :
```
pnpm add -D babel-plugin-module-resolver
```
修改 `babel.config.js` :
```js
module.exports = function (api) {
api.cache(true)
return {
presets: [
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
'nativewind/babel',
],
plugins: [
[
'module-resolver',
{
root: ['./src'],
alias: {
'@': './src',
},
},
],
],
}
}
```
@@ -0,0 +1,189 @@
## 云打包
1. 安装 EAS CLI
```bash
npm install -g eas-cli
```
2. 注册并登录 Expo 账户
- 访问 https://expo.dev/ 注册账户,然后命令行登录:
```bash
expo login
```
3. 配置项目构建
- 输入以下命令,选择 Android 平台即可。
```bash
eas build:configure
```
4. 构建 Android 包
- 默认构建 AAB 包(上传 Google Play):
```bash
eas build --platform android
```
- 构建 APK(可直接安装):
```bash
eas build -p android --profile preview
```
## 本地打包(EAS方式)
## 本地打包(原生方式)
### 1. 安装 [Android Studio]( https://developer.android.com/?hl=zh-cn)
1. 下载安装
- 按照官网提示依次安装 SDK 工具和必要组件。
- 安装完成后,记住 SDK 的安装路径(例如:`D:\data\sdk`,默认位置是:`C:\Users\<你的用户名>\AppData\Local\Android\Sdk`),后续需要用到。
2. 配置环境变量
- 打开 **系统属性 → 高级 → 环境变量**。
- 在 **系统变量** 中新增:
```
变量名:ANDROID_HOME
变量值:D:\data\sdk
```
- 在 **Path** 中新增:
```bash
%ANDROID_HOME%\platform-tools
%ANDROID_HOME%\tools
%ANDROID_HOME%\tools\bin
```
- 保存设置。
3. 验证安装
- 打开命令行,执行:
```
echo %ANDROID_HOME%
```
- 如果输出 SDK 路径,则说明配置成功。
### 2. 安装 [jdk17](https://adoptium.net/zh-CN/temurin/releases?version=8&os=any&arch=any)
1. 下载安装
- 下载对应版本解压即可。
- 安装成功后,记住 jdk 的安装路径,后续需要用到。
2. 配置环境变量
- 打开 **系统属性 → 高级 → 环境变量**。
- 在 **系统变量** 中新增:
```bash
变量名:JAVA_HOME
变量值:D:\devtool\jdk\jdk-17.0.16+8
```
- 在 **Path** 中新增:
```bash
%JAVA_HOME%\bin
```
- 保存设置。
3. 验证安装
- 打开命令行,执行:
```bash
java --version
```
- 如果输出 jdk 的信息,则说明配置成功。
### 3. Expo 项目启用 [Prebuild](https://docs.expo.dev/workflow/prebuild/)
1. 生成原生项目
```bash
npx expo prebuild
```
然后执行完毕后,会生成 `android` 目录。
2. 清除并重新生成原生项目
```bash
npx expo prebuild --clean
```
会删除已有的 `android` 和 `ios` 目录,然后重新生成。
## 打包体积优化
方法一:找到 `android/gradle.properties` 文件,找到 `reactNativeArchitectures` ,根据需要删除即可
```properties
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
```
方法二:原生方法,在 `android/app/build.gradle` 文件中添加或修改以下配置:
```
android {
// 其他配置...
splits {
abi {
enable true // 启用按 CPU 架构拆分
reset() // 重置默认架构列表
include 'arm64-v8a', 'armeabi-v7a'
universalApk false // 是否生成包含所有架构的通用 APK(false 表示只生成分架构的独立包)
// 还有一个 exclude 配置是排除指定架构的
}
}
// 其他配置...
}
```
**说明:**
- `enable true`:启用架构拆分功能。
- `reset()`:清空默认的架构配置,避免重复。
- `include`:指定要生成的架构包,比如只打 `arm64-v8a` 和 `armeabi-v7a`。
- `universalApk false`:不生成通用包(即包含所有架构的 APK)。
**举例:**
执行打包命令后,将生成多个包,例如:
```
app-arm64-v8a-release.apk
app-armeabi-v7a-release.apk
```
每个 APK 只包含对应架构的二进制文件,体积更小。
如果把 `universalApk` 设置为 `true`,则只会生成一个:
```
app-universal-release.apk
```
这个包能在 `arm64-v8a` 和 `armeabi-v7a` 架构的设备上运行,但体积最大。
@@ -0,0 +1,7 @@
{
"label": "expo",
"position": 1,
"link": {
"type": "generated-index"
}
}
@@ -0,0 +1,109 @@
NativeWind 原子化 CSS 库
> 官网:https://www.nativewind.dev/docs/getting-started/installation
1. 安装依赖
```bash
npx expo add nativewind react-native-reanimated react-native-safe-area-context
npx expo add tailwindcss@^3.4.17 -- --save-dev
```
2. 运行 `npx tailwindcss init` 生成 `tailwind.config.js` 文件,添加配置:
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
content: ['./App.tsx', './components/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
theme: {
extend: {},
},
plugins: [],
}
```
3. 创建 `global.css` 文件:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
注意在全局引入
4. 修改 `babel.config.js` 文件:
> 如果没有该文件,执行 `npx expo customize babel.config.js` 命令创建。
```js
module.exports = function (api) {
api.cache(true)
return {
presets: [
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
'nativewind/babel',
],
}
}
```
5. 修改 `metro.config.js` 文件:
> 如果没有该文件,执行 `npx expo customize metro.config.js` 命令创建。
```js
const { getDefaultConfig } = require('expo/metro-config')
const { withNativeWind } = require('nativewind/metro')
const config = getDefaultConfig(__dirname)
module.exports = withNativeWind(config, { input: './global.css' })
```
6. 新建 `src/types/nativewind-env.d.ts` 文件:
```ts
/// <reference types="nativewind/types" />
```
7. 安装 ` react-native-css-interop` :
```bash
npx expo install react-native-css-interop
```
8. 检验是否生效
```tsx
import { Text, View } from 'react-native'
import './global.css'
export default function App() {
return (
<View className="flex-1 items-center justify-center bg-white">
<Text className="text-xl font-bold text-blue-500">
Welcome to Nativewind!
</Text>
</View>
)
}
```
如果不生效,记得修改 `tailwind.config.js` 的目标路径:
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all files that contain Nativewind classes.
content: ['./src/app/_layout.tsx', './src/components/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
theme: {
extend: {},
},
plugins: [],
}
```
@@ -0,0 +1,290 @@
Gluestack UI UI 组件库
> 官网:https://gluestack.io/ui/docs/home/getting-started/installation
## 方式一:CLI
```bash
npx gluestack-ui init
```
## 方式二:Manual(推荐)
**前置条件:** 需要安装 `nativewind`
1. 安装依赖:
```bash
npx expo add @gluestack-ui/core @gluestack-ui/utils react-native-svg @gluestack/ui-next-adapter
```
2. 从 [github](https://github.com/gluestack/gluestack-ui/tree/main/src/components/ui/gluestack-ui-provider) 复制必须的组件到 `@/components/ui/gluestack-ui-provider`。
3. 修改 `tailwind.config.js` 文件:
```bash
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : 'class',
content: [
'./app/**/*.{html,js,jsx,ts,tsx,mdx}',
'./components/**/*.{html,js,jsx,ts,tsx,mdx}',
'./utils/**/*.{html,js,jsx,ts,tsx,mdx}',
'./*.{html,js,jsx,ts,tsx,mdx}',
'./src/**/*.{html,js,jsx,ts,tsx,mdx}',
],
presets: [require('nativewind/preset')],
important: 'html',
safelist: [
{
pattern:
// eslint-disable-next-line regexp/no-dupe-disjunctions
/(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/,
},
],
theme: {
extend: {
colors: {
primary: {
0: 'rgb(var(--color-primary-0)/<alpha-value>)',
50: 'rgb(var(--color-primary-50)/<alpha-value>)',
100: 'rgb(var(--color-primary-100)/<alpha-value>)',
200: 'rgb(var(--color-primary-200)/<alpha-value>)',
300: 'rgb(var(--color-primary-300)/<alpha-value>)',
400: 'rgb(var(--color-primary-400)/<alpha-value>)',
500: 'rgb(var(--color-primary-500)/<alpha-value>)',
600: 'rgb(var(--color-primary-600)/<alpha-value>)',
700: 'rgb(var(--color-primary-700)/<alpha-value>)',
800: 'rgb(var(--color-primary-800)/<alpha-value>)',
900: 'rgb(var(--color-primary-900)/<alpha-value>)',
950: 'rgb(var(--color-primary-950)/<alpha-value>)',
},
secondary: {
0: 'rgb(var(--color-secondary-0)/<alpha-value>)',
50: 'rgb(var(--color-secondary-50)/<alpha-value>)',
100: 'rgb(var(--color-secondary-100)/<alpha-value>)',
200: 'rgb(var(--color-secondary-200)/<alpha-value>)',
300: 'rgb(var(--color-secondary-300)/<alpha-value>)',
400: 'rgb(var(--color-secondary-400)/<alpha-value>)',
500: 'rgb(var(--color-secondary-500)/<alpha-value>)',
600: 'rgb(var(--color-secondary-600)/<alpha-value>)',
700: 'rgb(var(--color-secondary-700)/<alpha-value>)',
800: 'rgb(var(--color-secondary-800)/<alpha-value>)',
900: 'rgb(var(--color-secondary-900)/<alpha-value>)',
950: 'rgb(var(--color-secondary-950)/<alpha-value>)',
},
tertiary: {
50: 'rgb(var(--color-tertiary-50)/<alpha-value>)',
100: 'rgb(var(--color-tertiary-100)/<alpha-value>)',
200: 'rgb(var(--color-tertiary-200)/<alpha-value>)',
300: 'rgb(var(--color-tertiary-300)/<alpha-value>)',
400: 'rgb(var(--color-tertiary-400)/<alpha-value>)',
500: 'rgb(var(--color-tertiary-500)/<alpha-value>)',
600: 'rgb(var(--color-tertiary-600)/<alpha-value>)',
700: 'rgb(var(--color-tertiary-700)/<alpha-value>)',
800: 'rgb(var(--color-tertiary-800)/<alpha-value>)',
900: 'rgb(var(--color-tertiary-900)/<alpha-value>)',
950: 'rgb(var(--color-tertiary-950)/<alpha-value>)',
},
error: {
0: 'rgb(var(--color-error-0)/<alpha-value>)',
50: 'rgb(var(--color-error-50)/<alpha-value>)',
100: 'rgb(var(--color-error-100)/<alpha-value>)',
200: 'rgb(var(--color-error-200)/<alpha-value>)',
300: 'rgb(var(--color-error-300)/<alpha-value>)',
400: 'rgb(var(--color-error-400)/<alpha-value>)',
500: 'rgb(var(--color-error-500)/<alpha-value>)',
600: 'rgb(var(--color-error-600)/<alpha-value>)',
700: 'rgb(var(--color-error-700)/<alpha-value>)',
800: 'rgb(var(--color-error-800)/<alpha-value>)',
900: 'rgb(var(--color-error-900)/<alpha-value>)',
950: 'rgb(var(--color-error-950)/<alpha-value>)',
},
success: {
0: 'rgb(var(--color-success-0)/<alpha-value>)',
50: 'rgb(var(--color-success-50)/<alpha-value>)',
100: 'rgb(var(--color-success-100)/<alpha-value>)',
200: 'rgb(var(--color-success-200)/<alpha-value>)',
300: 'rgb(var(--color-success-300)/<alpha-value>)',
400: 'rgb(var(--color-success-400)/<alpha-value>)',
500: 'rgb(var(--color-success-500)/<alpha-value>)',
600: 'rgb(var(--color-success-600)/<alpha-value>)',
700: 'rgb(var(--color-success-700)/<alpha-value>)',
800: 'rgb(var(--color-success-800)/<alpha-value>)',
900: 'rgb(var(--color-success-900)/<alpha-value>)',
950: 'rgb(var(--color-success-950)/<alpha-value>)',
},
warning: {
0: 'rgb(var(--color-warning-0)/<alpha-value>)',
50: 'rgb(var(--color-warning-50)/<alpha-value>)',
100: 'rgb(var(--color-warning-100)/<alpha-value>)',
200: 'rgb(var(--color-warning-200)/<alpha-value>)',
300: 'rgb(var(--color-warning-300)/<alpha-value>)',
400: 'rgb(var(--color-warning-400)/<alpha-value>)',
500: 'rgb(var(--color-warning-500)/<alpha-value>)',
600: 'rgb(var(--color-warning-600)/<alpha-value>)',
700: 'rgb(var(--color-warning-700)/<alpha-value>)',
800: 'rgb(var(--color-warning-800)/<alpha-value>)',
900: 'rgb(var(--color-warning-900)/<alpha-value>)',
950: 'rgb(var(--color-warning-950)/<alpha-value>)',
},
info: {
0: 'rgb(var(--color-info-0)/<alpha-value>)',
50: 'rgb(var(--color-info-50)/<alpha-value>)',
100: 'rgb(var(--color-info-100)/<alpha-value>)',
200: 'rgb(var(--color-info-200)/<alpha-value>)',
300: 'rgb(var(--color-info-300)/<alpha-value>)',
400: 'rgb(var(--color-info-400)/<alpha-value>)',
500: 'rgb(var(--color-info-500)/<alpha-value>)',
600: 'rgb(var(--color-info-600)/<alpha-value>)',
700: 'rgb(var(--color-info-700)/<alpha-value>)',
800: 'rgb(var(--color-info-800)/<alpha-value>)',
900: 'rgb(var(--color-info-900)/<alpha-value>)',
950: 'rgb(var(--color-info-950)/<alpha-value>)',
},
typography: {
0: 'rgb(var(--color-typography-0)/<alpha-value>)',
50: 'rgb(var(--color-typography-50)/<alpha-value>)',
100: 'rgb(var(--color-typography-100)/<alpha-value>)',
200: 'rgb(var(--color-typography-200)/<alpha-value>)',
300: 'rgb(var(--color-typography-300)/<alpha-value>)',
400: 'rgb(var(--color-typography-400)/<alpha-value>)',
500: 'rgb(var(--color-typography-500)/<alpha-value>)',
600: 'rgb(var(--color-typography-600)/<alpha-value>)',
700: 'rgb(var(--color-typography-700)/<alpha-value>)',
800: 'rgb(var(--color-typography-800)/<alpha-value>)',
900: 'rgb(var(--color-typography-900)/<alpha-value>)',
950: 'rgb(var(--color-typography-950)/<alpha-value>)',
white: '#FFFFFF',
gray: '#D4D4D4',
black: '#181718',
},
outline: {
0: 'rgb(var(--color-outline-0)/<alpha-value>)',
50: 'rgb(var(--color-outline-50)/<alpha-value>)',
100: 'rgb(var(--color-outline-100)/<alpha-value>)',
200: 'rgb(var(--color-outline-200)/<alpha-value>)',
300: 'rgb(var(--color-outline-300)/<alpha-value>)',
400: 'rgb(var(--color-outline-400)/<alpha-value>)',
500: 'rgb(var(--color-outline-500)/<alpha-value>)',
600: 'rgb(var(--color-outline-600)/<alpha-value>)',
700: 'rgb(var(--color-outline-700)/<alpha-value>)',
800: 'rgb(var(--color-outline-800)/<alpha-value>)',
900: 'rgb(var(--color-outline-900)/<alpha-value>)',
950: 'rgb(var(--color-outline-950)/<alpha-value>)',
},
background: {
0: 'rgb(var(--color-background-0)/<alpha-value>)',
50: 'rgb(var(--color-background-50)/<alpha-value>)',
100: 'rgb(var(--color-background-100)/<alpha-value>)',
200: 'rgb(var(--color-background-200)/<alpha-value>)',
300: 'rgb(var(--color-background-300)/<alpha-value>)',
400: 'rgb(var(--color-background-400)/<alpha-value>)',
500: 'rgb(var(--color-background-500)/<alpha-value>)',
600: 'rgb(var(--color-background-600)/<alpha-value>)',
700: 'rgb(var(--color-background-700)/<alpha-value>)',
800: 'rgb(var(--color-background-800)/<alpha-value>)',
900: 'rgb(var(--color-background-900)/<alpha-value>)',
950: 'rgb(var(--color-background-950)/<alpha-value>)',
error: 'rgb(var(--color-background-error)/<alpha-value>)',
warning: 'rgb(var(--color-background-warning)/<alpha-value>)',
muted: 'rgb(var(--color-background-muted)/<alpha-value>)',
success: 'rgb(var(--color-background-success)/<alpha-value>)',
info: 'rgb(var(--color-background-info)/<alpha-value>)',
light: '#FBFBFB',
dark: '#181719',
},
indicator: {
primary: 'rgb(var(--color-indicator-primary)/<alpha-value>)',
info: 'rgb(var(--color-indicator-info)/<alpha-value>)',
error: 'rgb(var(--color-indicator-error)/<alpha-value>)',
},
},
fontFamily: {
'heading': undefined,
'body': undefined,
'mono': undefined,
'jakarta': ['var(--font-plus-jakarta-sans)'],
'roboto': ['var(--font-roboto)'],
'code': ['var(--font-source-code-pro)'],
'inter': ['var(--font-inter)'],
'space-mono': ['var(--font-space-mono)'],
},
fontWeight: {
extrablack: '950',
},
fontSize: {
'2xs': '10px',
},
boxShadow: {
'hard-1': '-2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
'hard-2': '0px 3px 10px 0px rgba(38, 38, 38, 0.20)',
'hard-3': '2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
'hard-4': '0px -3px 10px 0px rgba(38, 38, 38, 0.20)',
'hard-5': '0px 2px 10px 0px rgba(38, 38, 38, 0.10)',
'soft-1': '0px 0px 10px rgba(38, 38, 38, 0.1)',
'soft-2': '0px 0px 20px rgba(38, 38, 38, 0.2)',
'soft-3': '0px 0px 30px rgba(38, 38, 38, 0.1)',
'soft-4': '0px 0px 40px rgba(38, 38, 38, 0.1)',
},
},
},
}
```
4. 使用 `GluestackUIProvider` 包括着整个组件:
```tsx
// App.tsx
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
import '@/global.css' // Make sure this path is correct
export default function App() {
return (
<GluestackUIProvider>{/* Add your app code here */}</GluestackUIProvider>
)
}
```
## 测试
下载对应组件:
```bash
npx gluestack-ui add button
```
引入使用:
```tsx
import { StyleSheet, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { Button, ButtonText } from '@/components/ui/button'
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
import '@/styles/global.css'
export default function Layout() {
return (
<GluestackUIProvider>
<SafeAreaView style={styles.container}>
<View className="flex-1 items-center justify-center bg-white">
<Text className="text-xl font-bold text-blue-500">
Welcome to Nativewind!
</Text>
<Button>
<ButtonText>你好</ButtonText>
</Button>
</View>
</SafeAreaView>
</GluestackUIProvider>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
paddingHorizontal: 16,
}
})
```
@@ -0,0 +1,6 @@
Lucide 是一个轻量级、开源的图标库
> 官网:https://lucide.dev/icons/
```bash
npx expo add lucide-react-native
```
@@ -0,0 +1,7 @@
{
"label": "第三方库",
"position": 2,
"link": {
"type": "generated-index"
}
}
@@ -0,0 +1,34 @@
Docusaurus 是一个开源的静态网站生成器,专注于帮助开发者快速构建、部署和维护文档类网站。
> 官网:https://docusaurus.io/zh-CN/
## 优点
- 高度自定义:
- 使用 react 开发,并支持自定义扩展组件或自定义页面
- 提供 swizzle 命令可完全自定义布局
- 基础页面干净整洁,可快速捕捉到关键信息
- 社区群庞大,拥有广大的社区支持
- 同时支持 docs(文档) 和 blog(博客),能够有更多的选择
## 快速开始
### 1.环境要求
- Node版本最低 20
### 2.安装
```bash
npx create-docusaurus@latest my-website classic
```
如果需要支持 `typescript`,则使用
```bash
npx create-docusaurus@latest my-website classic --typescript
```
### 3.运行
下载依赖
```bash
pnpm i
```
运行项目
```bash
pnpm run start
```
@@ -0,0 +1,35 @@
默认的项目结构如下
```text
my-website
├── blog
│ ├── 2019-05-28-hola.md
│ ├── 2019-05-29-hello-world.md
│ └── 2020-05-30-welcome.md
├── docs
│ ├── doc1.md
│ ├── doc2.md
│ ├── doc3.md
│ └── mdx.md
├── src
│ ├── css
│ │ └── custom.css
│ └── pages
│ ├── styles.module.css
│ └── index.ts
├── static
│ └── img
├── docusaurus.config.ts
├── package.json
├── README.md
├── sidebars.ts
└── yarn.lock
```
目录介绍:
- `blog` - 存放博客文章,格式是 Markdown
- `docs` - 存放文档内容,也用 Markdown 格式
- `src` - 存放自定义的页面和样式。
- `static` - 存放自定义的页面和样式
- `docusaurus.config.ts` - 配置网站的各种信息
- `sidebars.ts` - 配置 docs 侧边栏的内容和结构
@@ -0,0 +1,123 @@
关于文档的创建和配置
## 1. navbar 配置
在 `docusaurus.config.ts` 文件中进行配置, `config` - `themeConfig` - `navbar` - `items`
### 1.1 单级导航
在 `docs` 文件夹下创建 `test.md` 文件,并在 `items` 配置项中添加:
```json
{
// ...
"items": [
{
"to": "/docs/test",
"label": "test"
}
]
}
```
刷新页面,即可进行预览
### 1.2 多级导航
docusaurus 的 navbar 最多支持两级导航,比如:
```json
{
// ...
"items": [
{
"type": "dropdown",
"label": "编程语言",
"position": "left",
"items": [
{
"label": "Stack Overflow",
"href": "https://stackoverflow.com/questions/tagged/docusaurus"
},
{
"label": "Discord",
"href": "https://discordapp.com/invite/docusaurus"
}
]
}
]
}
```
## 2. 侧边栏配置
docs 的侧边栏配置主要在 `sidebars.ts` 文件中
### 2.1 自动生成
在 docs 目录中创建 `coding/java` 目录,在 `sidebar.ts` 文件中添加配置:
```ts
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'
const sidebars: SidebarsConfig = {
// coding 目录下的自动生成的 Java 相关文档
javaSidebar: [{ type: 'autogenerated', dirName: 'coding/java' }]
}
export default sidebars
```
修改 `docusaurus.config.ts` 文件:
```json
{
// ...
"items": [
{
"type": "dropdown",
"label": "编程语言",
"position": "left",
"items": [
{
"type": "docSidebar",
"label": "Java",
"sidebarId": "javaSidebar"
}
]
}
]
}
```
提示:
- `sidebarId` 需要和 `SidebarsConfig` 中的 key 保持一致
- `type: 'autogenerated'` 表示自动生成
- **自动生成模式下 将自动根据目录和目录下的文件自动构建侧边栏**
### 2.2 手动配置
```ts
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'
const sidebars: SidebarsConfig = {
// 手动配置的侧边栏
javaSidebar: [
{
type: 'category',
label: 'Java 基础',
items: ['coding/java/intro', 'coding/java/advanced'],
},
{
type: 'category',
label: 'Java 框架',
items: ['coding/java/spring-boot', 'coding/java/hibernate'],
},
]
}
export default sidebars
```
Whitespace-only changes.
@@ -0,0 +1,119 @@
> 具体可参考:https://docusaurus.io/zh-CN/docs/search
Algolia 是一个强大的搜索引擎平台,专注于提供快速、可定制的搜索功能。它通常用于为网站、应用或文档提供实时搜索和自动完成功能。
## 1. 获取 appId 和 apiKey
1. 先到 [官网](https://dashboard.algolia.com) 注册,大概两天后会收到邮件通知注册成功,再接着往下操作
2. 登录
3. 创建一个新的 Application,选择左边的免费版本进行下一步
4. 定义 index_name,选择响应快的服务进行创建。到这一步就创建成功了
5. 打开 API keys,获取三个需要的字段 Application ID、Search-Only API key、Admin API key
## 2. 修改 docusaurus.config.js
```json
{
"config": {
"themeConfig": {
"algolia": {
"appId": "填写申请的appId",
"apiKey": "填写申请的apiKey",
"indexName": "填写申请的indexName"
}
}
}
}
```
这样就会自动在 `navbar` 添加个搜索框
## 3. 新增 `docsearch.json` 配置文件
注意将信息换成自己的信息
```json
{
// 需要替换
"index_name": "xxx",
// 需要替换。网站网址
"start_urls": ["xxx"],
// 需要替换。sitemap的网址,docusaurus 默认在根目录下生成 sitemap.xml
"sitemap_urls": ["xxx"],
"selectors": {
"lvl0": {
"selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[last()]",
"type": "xpath",
"global": true,
"default_value": "Documentation"
},
"lvl1": "header h1, article h1",
"lvl2": "article h2",
"lvl3": "article h3",
"lvl4": "article h4",
"lvl5": "article h5, article td:first-child",
"lvl6": "article h6",
"text": "article p, article li, article td:last-child"
},
"custom_settings": {
"attributesForFaceting": [
"type",
"lang",
"language",
"version",
"docusaurus_tag"
],
"attributesToRetrieve": [
"hierarchy",
"content",
"anchor",
"url",
"url_without_anchor",
"type"
],
"attributesToHighlight": ["hierarchy", "content"],
"attributesToSnippet": ["content:10"],
"camelCaseAttributes": ["hierarchy", "content"],
"searchableAttributes": [
"unordered(hierarchy.lvl0)",
"unordered(hierarchy.lvl1)",
"unordered(hierarchy.lvl2)",
"unordered(hierarchy.lvl3)",
"unordered(hierarchy.lvl4)",
"unordered(hierarchy.lvl5)",
"unordered(hierarchy.lvl6)",
"content"
],
"distinct": true,
"attributeForDistinct": "url",
"customRanking": [
"desc(weight.pageRank)",
"desc(weight.level)",
"asc(weight.position)"
],
"ranking": [
"words",
"filters",
"typo",
"attribute",
"proximity",
"exact",
"custom"
],
"highlightPreTag": "<span class='algolia-docsearch-suggestion--highlight'>",
"highlightPostTag": "</span>",
"minWordSizefor1Typo": 3,
"minWordSizefor2Typos": 7,
"allowTyposOnNumericTokens": false,
"minProximity": 1,
"ignorePlurals": true,
"advancedSyntax": true,
"attributeCriteriaComputedByMinProximity": true,
"removeWordsIfNoResults": "allOptional",
"separatorsToIndex": "_",
"synonyms": [
["js", "javascript"],
["ts", "typescript"]
]
}
}
```
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 KiB

-47
View File
@@ -1,47 +0,0 @@
---
sidebar_position: 1
---
# Tutorial Intro
Let's discover **Docusaurus in less than 5 minutes**.
## Getting Started
Get started by **creating a new site**.
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
### What you'll need
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
## Generate a new site
Generate a new Docusaurus site using the **classic template**.
The classic template will automatically be added to your project after you run the command:
```bash
npm init docusaurus@latest my-website classic
```
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
## Start your site
Run the development server:
```bash
cd my-website
npm run start
```
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
View File
Whitespace-only changes.
@@ -1,8 +0,0 @@
{
"label": "Tutorial - Basics",
"position": 2,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}
@@ -1,23 +0,0 @@
---
sidebar_position: 6
---
# Congratulations!
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
Docusaurus has **much more to offer**!
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
## What's next?
- Read the [official documentation](https://docusaurus.io/)
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
@@ -1,34 +0,0 @@
---
sidebar_position: 3
---
# Create a Blog Post
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
## Create your first Post
Create a file at `blog/2021-02-28-greetings.md`:
```md title="blog/2021-02-28-greetings.md"
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much as you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
@@ -1,57 +0,0 @@
---
sidebar_position: 2
---
# Create a Document
Documents are **groups of pages** connected through:
- a **sidebar**
- **previous/next navigation**
- **versioning**
## Create your first Doc
Create a Markdown file at `docs/hello.md`:
```md title="docs/hello.md"
# Hello
This is my **first Docusaurus document**!
```
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
## Configure the Sidebar
Docusaurus automatically **creates a sidebar** from the `docs` folder.
Add metadata to customize the sidebar label and position:
```md title="docs/hello.md" {1-4}
---
sidebar_label: "Hi!"
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
```
It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
export default {
tutorialSidebar: [
"intro",
// highlight-next-line
"hello",
{
type: "category",
label: "Tutorial",
items: ["tutorial-basics/create-a-document"],
},
],
};
```
@@ -1,43 +0,0 @@
---
sidebar_position: 1
---
# Create a Page
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
- `src/pages/index.js` → `localhost:3000/`
- `src/pages/foo.md` → `localhost:3000/foo`
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
## Create your first React Page
Create a file at `src/pages/my-react-page.js`:
```jsx title="src/pages/my-react-page.js"
import React from "react";
import Layout from "@theme/Layout";
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
```
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
## Create your first Markdown Page
Create a file at `src/pages/my-markdown-page.md`:
```mdx title="src/pages/my-markdown-page.md"
# My Markdown page
This is a Markdown page
```
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
@@ -1,31 +0,0 @@
---
sidebar_position: 5
---
# Deploy your site
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
It builds your site as simple **static HTML, JavaScript and CSS files**.
## Build your site
Build your site **for production**:
```bash
npm run build
```
The static files are generated in the `build` folder.
## Deploy your site
Test your production build locally:
```bash
npm run serve
```
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
@@ -1,153 +0,0 @@
---
sidebar_position: 4
---
# Markdown Features
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
## Front Matter
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
```text title="my-doc.md"
// highlight-start
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
// highlight-end
## Markdown heading
Markdown text with [links](./hello.md)
```
## Links
Regular Markdown links are supported, using url paths or relative file paths.
```md
Let's see how to [Create a page](/create-a-page).
```
```md
Let's see how to [Create a page](./create-a-page.md).
```
**Result:** Let's see how to [Create a page](./create-a-page.md).
## Images
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
```md
![Docusaurus logo](/img/docusaurus.png)
```
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
```md
![Docusaurus logo](./img/docusaurus.png)
```
## Code Blocks
Markdown code blocks are supported with Syntax highlighting.
````md
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
````
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
## Admonitions
Docusaurus has a special syntax to create admonitions and callouts:
```md
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
```
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
## MDX and React Components
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
```jsx
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
```
export const Highlight = ({ children, color }) => (
<span
style={{
backgroundColor: color,
borderRadius: "20px",
color: "#fff",
padding: "10px",
cursor: "pointer",
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`);
}}
>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

@@ -1,55 +0,0 @@
---
sidebar_position: 1
---
# Manage Docs Versions
Docusaurus can manage multiple versions of your docs.
## Create a docs version
Release a version 1.0 of your project:
```bash
npm run docusaurus docs:version 1.0
```
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
Your docs now have 2 versions:
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
## Add a Version Dropdown
To navigate seamlessly across versions, add a version dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: "docsVersionDropdown",
},
// highlight-end
],
},
},
};
```
The docs version dropdown appears in your navbar:
![Docs Version Dropdown](./img/docsVersionDropdown.png)
## Update an existing version
It is possible to edit versioned docs in their respective folder:
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
@@ -1,88 +0,0 @@
---
sidebar_position: 2
---
# Translate your site
Let's translate `docs/intro.md` to French.
## Configure i18n
Modify `docusaurus.config.js` to add support for the `fr` locale:
```js title="docusaurus.config.js"
export default {
i18n: {
defaultLocale: "en",
locales: ["en", "fr"],
},
};
```
## Translate a doc
Copy the `docs/intro.md` file to the `i18n/fr` folder:
```bash
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
```
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
## Start your localized site
Start your site on the French locale:
```bash
npm run start -- --locale fr
```
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
:::caution
In development, you can only use one locale at a time.
:::
## Add a Locale Dropdown
To navigate seamlessly across languages, add a locale dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: "localeDropdown",
},
// highlight-end
],
},
},
};
```
The locale dropdown now appears in your navbar:
![Locale Dropdown](./img/localeDropdown.png)
## Build your localized site
Build your site for a specific locale:
```bash
npm run build -- --locale fr
```
Or build your site to include all the locales at once:
```bash
npm run build
```
+32 -4
View File
@@ -32,7 +32,7 @@ const config: Config = {
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'zh-Hans',
locales: ['zh-Hans', 'en'],
locales: ['zh-Hans'],
},
presets: [
[
@@ -82,10 +82,38 @@ const config: Config = {
hideOnScroll: true,
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
type: 'dropdown',
label: '编程语言',
position: 'left',
label: '文档',
items: [
{
type: 'docSidebar',
label: 'Java',
sidebarId: 'JavaSidebar',
},
{
type: 'docSidebar',
label: 'ReactNative',
sidebarId: 'ReactNativeSidebar',
},
],
},
{
type: 'dropdown',
label: '开发工具',
position: 'left',
items: [
{
type: 'docSidebar',
label: 'Docusaurus',
sidebarId: 'DocusaurusSidebar',
},
{
type: 'docSidebar',
label: 'Git',
sidebarId: 'GitSidebar',
},
],
},
{
to: '/blog',
+3
View File
@@ -10,4 +10,7 @@ export default antfu({
extends: [
'plugin:@docusaurus/recommended',
],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
})
+6 -26
View File
@@ -1,33 +1,13 @@
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// coding
JavaSidebar: [{ type: "autogenerated", dirName: "coding/java" }],
ReactNativeSidebar: [{ type: "autogenerated", dirName: "coding/react-native" }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
// dev-tools
DocusaurusSidebar: [{ type: "autogenerated", dirName: "dev-tools/docusaurus" }],
GitSidebar: [{ type: "autogenerated", dirName: "dev-tools/git" }],
};
export default sidebars;
+2 -2
View File
@@ -18,9 +18,9 @@ function HomepageHeader() {
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link className="button button--secondary button--lg" to="/docs/intro">
{/* <Link className="button button--secondary button--lg" to="/docs/intro">
Docusaurus Tutorial - 5min ⏱️
</Link>
</Link> */}
</div>
</div>
</header>
@@ -1,68 +0,0 @@
import React, {useState, useRef, useEffect} from 'react';
import clsx from 'clsx';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import NavbarItem from '@theme/NavbarItem';
export default function DropdownNavbarItemDesktop({
items,
position,
className,
onClick,
...props
}) {
const dropdownRef = useRef(null);
const [showDropdown, setShowDropdown] = useState(false);
useEffect(() => {
const handleClickOutside = (event) => {
if (!dropdownRef.current || dropdownRef.current.contains(event.target)) {
return;
}
setShowDropdown(false);
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
document.addEventListener('focusin', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
document.removeEventListener('focusin', handleClickOutside);
};
}, [dropdownRef]);
return (
<div
ref={dropdownRef}
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
'dropdown--right': position === 'right',
'dropdown--show': showDropdown,
})}>
<NavbarNavLink
aria-haspopup="true"
aria-expanded={showDropdown}
role="button"
// # hash permits to make the <a> tag focusable in case no link target
// See https://github.com/facebook/docusaurus/pull/6003
// There's probably a better solution though...
href={props.to ? undefined : '#'}
className={clsx('navbar__link', className)}
{...props}
onClick={props.to ? undefined : (e) => e.preventDefault()}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
setShowDropdown(!showDropdown);
}
}}>
{props.children ?? props.label}
</NavbarNavLink>
<ul className="dropdown__menu">
{items.map((childItemProps, i) => (
<NavbarItem
isDropdownItem
activeClassName="dropdown__link--active"
{...childItemProps}
key={i}
/>
))}
</ul>
</div>
);
}
@@ -1,82 +0,0 @@
import type { Props } from '@theme/NavbarItem/DropdownNavbarItem/Desktop'
import type { ReactNode } from 'react'
import NavbarItem from '@theme/NavbarItem'
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'
import clsx from 'clsx'
import React, { useEffect, useRef, useState } from 'react'
export default function DropdownNavbarItemDesktop({
items,
position,
className,
onClick,
...props
}: Props): ReactNode {
const dropdownRef = useRef<HTMLDivElement>(null)
const [showDropdown, setShowDropdown] = useState(false)
useEffect(() => {
const handleClickOutside = (
event: MouseEvent | TouchEvent | FocusEvent,
) => {
if (
!dropdownRef.current
|| dropdownRef.current.contains(event.target as Node)
) {
return
}
setShowDropdown(false)
}
document.addEventListener('mousedown', handleClickOutside)
document.addEventListener('touchstart', handleClickOutside)
document.addEventListener('focusin', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
document.removeEventListener('touchstart', handleClickOutside)
document.removeEventListener('focusin', handleClickOutside)
}
}, [dropdownRef])
return (
<div
ref={dropdownRef}
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
'dropdown--right': position === 'right',
'dropdown--show': showDropdown,
})}
>
<NavbarNavLink
aria-haspopup="true"
aria-expanded={showDropdown}
role="button"
// # hash permits to make the <a> tag focusable in case no link target
// See https://github.com/facebook/docusaurus/pull/6003
// There's probably a better solution though...
href={props.to ? undefined : '#'}
className={clsx('navbar__link', className)}
{...props}
onClick={props.to ? undefined : e => e.preventDefault()}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
setShowDropdown(!showDropdown)
}
}}
>
{props.children ?? props.label}
</NavbarNavLink>
<ul className="dropdown__menu">
{items.map((childItemProps, i) => (
<NavbarItem
isDropdownItem
activeClassName="dropdown__link--active"
{...childItemProps}
key={i}
/>
))}
</ul>
</div>
)
}
@@ -1,136 +0,0 @@
import React, {useEffect} from 'react';
import clsx from 'clsx';
import {
isRegexpStringMatch,
useCollapsible,
Collapsible,
} from '@docusaurus/theme-common';
import {isSamePath, useLocalPathname} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import NavbarItem from '@theme/NavbarItem';
import styles from './styles.module.css';
function isItemActive(item, localPathname) {
if (isSamePath(item.to, localPathname)) {
return true;
}
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
return true;
}
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
return true;
}
return false;
}
function containsActiveItems(items, localPathname) {
return items.some((item) => isItemActive(item, localPathname));
}
function CollapseButton({collapsed, onClick}) {
return (
<button
aria-label={
collapsed
? translate({
id: 'theme.navbar.mobileDropdown.collapseButton.expandAriaLabel',
message: 'Expand the dropdown',
description:
'The ARIA label of the button to expand the mobile dropdown navbar item',
})
: translate({
id: 'theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel',
message: 'Collapse the dropdown',
description:
'The ARIA label of the button to collapse the mobile dropdown navbar item',
})
}
aria-expanded={!collapsed}
type="button"
className="clean-btn menu__caret"
onClick={onClick}
/>
);
}
function useItemCollapsible({active}) {
const {collapsed, toggleCollapsed, setCollapsed} = useCollapsible({
initialState: () => !active,
});
// Expand if any item active after a navigation
useEffect(() => {
if (active) {
setCollapsed(false);
}
}, [active, setCollapsed]);
return {
collapsed,
toggleCollapsed,
};
}
export default function DropdownNavbarItemMobile({
items,
className,
position, // Need to destructure position from props so that it doesn't get passed on.
onClick,
...props
}) {
const localPathname = useLocalPathname();
const isActive = isSamePath(props.to, localPathname);
const containsActive = containsActiveItems(items, localPathname);
const {collapsed, toggleCollapsed} = useItemCollapsible({
active: isActive || containsActive,
});
// # hash permits to make the <a> tag focusable in case no link target
// See https://github.com/facebook/docusaurus/pull/6003
// There's probably a better solution though...
const href = props.to ? undefined : '#';
return (
<li
className={clsx('menu__list-item', {
'menu__list-item--collapsed': collapsed,
})}>
<div
className={clsx('menu__list-item-collapsible', {
'menu__list-item-collapsible--active': isActive,
})}>
<NavbarNavLink
role="button"
className={clsx(
styles.dropdownNavbarItemMobile,
'menu__link menu__link--sublist',
className,
)}
href={href}
{...props}
onClick={(e) => {
// Prevent navigation when link is "#"
if (href === '#') {
e.preventDefault();
}
// Otherwise we let navigation eventually happen, and/or collapse
toggleCollapsed();
}}>
{props.children ?? props.label}
</NavbarNavLink>
<CollapseButton
collapsed={collapsed}
onClick={(e) => {
e.preventDefault();
toggleCollapsed();
}}
/>
</div>
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
{items.map((childItemProps, i) => (
<NavbarItem
mobile
isDropdownItem
onClick={onClick}
activeClassName="menu__link--active"
{...childItemProps}
key={i}
/>
))}
</Collapsible>
</li>
);
}
@@ -1,159 +0,0 @@
import React, {useEffect, type ReactNode, type ComponentProps} from 'react';
import clsx from 'clsx';
import {
isRegexpStringMatch,
useCollapsible,
Collapsible,
} from '@docusaurus/theme-common';
import {isSamePath, useLocalPathname} from '@docusaurus/theme-common/internal';
import {translate} from '@docusaurus/Translate';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem';
import type {Props} from '@theme/NavbarItem/DropdownNavbarItem/Mobile';
import styles from './styles.module.css';
function isItemActive(
item: LinkLikeNavbarItemProps,
localPathname: string,
): boolean {
if (isSamePath(item.to, localPathname)) {
return true;
}
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
return true;
}
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
return true;
}
return false;
}
function containsActiveItems(
items: readonly LinkLikeNavbarItemProps[],
localPathname: string,
): boolean {
return items.some((item) => isItemActive(item, localPathname));
}
function CollapseButton({
collapsed,
onClick,
}: {
collapsed: boolean;
onClick: ComponentProps<'button'>['onClick'];
}) {
return (
<button
aria-label={
collapsed
? translate({
id: 'theme.navbar.mobileDropdown.collapseButton.expandAriaLabel',
message: 'Expand the dropdown',
description:
'The ARIA label of the button to expand the mobile dropdown navbar item',
})
: translate({
id: 'theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel',
message: 'Collapse the dropdown',
description:
'The ARIA label of the button to collapse the mobile dropdown navbar item',
})
}
aria-expanded={!collapsed}
type="button"
className="clean-btn menu__caret"
onClick={onClick}
/>
);
}
function useItemCollapsible({active}: {active: boolean}) {
const {collapsed, toggleCollapsed, setCollapsed} = useCollapsible({
initialState: () => !active,
});
// Expand if any item active after a navigation
useEffect(() => {
if (active) {
setCollapsed(false);
}
}, [active, setCollapsed]);
return {
collapsed,
toggleCollapsed,
};
}
export default function DropdownNavbarItemMobile({
items,
className,
position, // Need to destructure position from props so that it doesn't get passed on.
onClick,
...props
}: Props): ReactNode {
const localPathname = useLocalPathname();
const isActive = isSamePath(props.to, localPathname);
const containsActive = containsActiveItems(items, localPathname);
const {collapsed, toggleCollapsed} = useItemCollapsible({
active: isActive || containsActive,
});
// # hash permits to make the <a> tag focusable in case no link target
// See https://github.com/facebook/docusaurus/pull/6003
// There's probably a better solution though...
const href = props.to ? undefined : '#';
return (
<li
className={clsx('menu__list-item', {
'menu__list-item--collapsed': collapsed,
})}>
<div
className={clsx('menu__list-item-collapsible', {
'menu__list-item-collapsible--active': isActive,
})}>
<NavbarNavLink
role="button"
className={clsx(
styles.dropdownNavbarItemMobile,
'menu__link menu__link--sublist',
className,
)}
href={href}
{...props}
onClick={(e) => {
// Prevent navigation when link is "#"
if (href === '#') {
e.preventDefault();
}
// Otherwise we let navigation eventually happen, and/or collapse
toggleCollapsed();
}}>
{props.children ?? props.label}
</NavbarNavLink>
<CollapseButton
collapsed={collapsed}
onClick={(e) => {
e.preventDefault();
toggleCollapsed();
}}
/>
</div>
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
{items.map((childItemProps, i) => (
<NavbarItem
mobile
isDropdownItem
onClick={onClick}
activeClassName="menu__link--active"
{...childItemProps}
key={i}
/>
))}
</Collapsible>
</li>
);
}
@@ -1,3 +0,0 @@
.dropdownNavbarItemMobile {
cursor: pointer;
}
@@ -1,8 +0,0 @@
import DropdownNavbarItemDesktop from '@theme/NavbarItem/DropdownNavbarItem/Desktop'
import DropdownNavbarItemMobile from '@theme/NavbarItem/DropdownNavbarItem/Mobile'
import React from 'react'
export default function DropdownNavbarItem({ mobile = false, ...props }) {
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop
return <Comp {...props} />
}
@@ -1,13 +0,0 @@
import type { Props } from '@theme/NavbarItem/DropdownNavbarItem'
import type { ReactNode } from 'react'
import DropdownNavbarItemDesktop from '@theme/NavbarItem/DropdownNavbarItem/Desktop'
import DropdownNavbarItemMobile from '@theme/NavbarItem/DropdownNavbarItem/Mobile'
import React from 'react'
export default function DropdownNavbarItem({
mobile = false,
...props
}: Props): ReactNode {
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop
return <Comp {...props} />
}