Restructure docs navigation for channel and widget guides

This commit is contained in:
mlogclub
2026-06-09 19:50:41 +08:00
parent a6ae0fbd3d
commit 62d3ca64bb
6 changed files with 48 additions and 40 deletions
+1 -1
Submodule docs updated: 751a56bdfd...b99f88541a
+17 -14
View File
@@ -15,6 +15,7 @@ import { useAppLocale, useI18n } from "@/i18n/provider"
import { Status } from "@/lib/generated/enums" import { Status } from "@/lib/generated/enums"
import { getRoleDisplayName } from "@/lib/role-i18n" import { getRoleDisplayName } from "@/lib/role-i18n"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ImageInput } from "@/components/image-input"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
@@ -119,13 +120,7 @@ function CreateUserDrawerBody({
z.object({ z.object({
username: z.string().trim().min(1, t("user.usernameRequired")), username: z.string().trim().min(1, t("user.usernameRequired")),
nickname: z.string().trim(), nickname: z.string().trim(),
avatar: z avatar: z.string().trim(),
.string()
.trim()
.refine(
(value) => value.length === 0 || /^https?:\/\/\S+$/i.test(value),
t("user.avatarInvalid")
),
mobile: z mobile: z
.string() .string()
.trim() .trim()
@@ -233,14 +228,22 @@ function CreateUserDrawerBody({
<FieldError errors={[errors.nickname]} /> <FieldError errors={[errors.nickname]} />
</FieldContent> </FieldContent>
</Field> </Field>
<Field data-invalid={!!errors.avatar}> <Field className="min-h-32" data-invalid={!!errors.avatar}>
<FieldLabel htmlFor="create-avatar">{t("user.avatar")}</FieldLabel> <FieldLabel>{t("user.avatar")}</FieldLabel>
<FieldContent> <FieldContent>
<Input <Controller
id="create-avatar" control={control}
placeholder={t("user.avatarCreatePlaceholder")} name="avatar"
aria-invalid={!!errors.avatar} render={({ field }) => (
{...register("avatar")} <ImageInput
value={field.value}
onChange={field.onChange}
disabled={saving}
prefix="avatar"
placeholder={t("user.avatarCreatePlaceholder")}
className="size-16 rounded-full"
/>
)}
/> />
<FieldError errors={[errors.avatar]} /> <FieldError errors={[errors.avatar]} />
</FieldContent> </FieldContent>
+19 -15
View File
@@ -2,7 +2,7 @@
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState } from "react"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { Resolver, useForm } from "react-hook-form" import { Controller, Resolver, useForm } from "react-hook-form"
import { z } from "zod/v4" import { z } from "zod/v4"
import { import {
@@ -11,6 +11,7 @@ import {
fetchUserDetail, fetchUserDetail,
} from "@/lib/api/admin" } from "@/lib/api/admin"
import { useI18n } from "@/i18n/provider" import { useI18n } from "@/i18n/provider"
import { ImageInput } from "@/components/image-input"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
Drawer, Drawer,
@@ -121,13 +122,7 @@ function UserEditDrawerBody({
() => () =>
z.object({ z.object({
nickname: z.string().trim().min(1, t("user.nicknameRequired")), nickname: z.string().trim().min(1, t("user.nicknameRequired")),
avatar: z avatar: z.string().trim(),
.string()
.trim()
.refine(
(value) => value.length === 0 || /^https?:\/\/\S+$/i.test(value),
t("user.avatarInvalid")
),
mobile: z mobile: z
.string() .string()
.trim() .trim()
@@ -155,6 +150,7 @@ function UserEditDrawerBody({
defaultValues: emptyForm, defaultValues: emptyForm,
}) })
const { const {
control,
handleSubmit, handleSubmit,
reset, reset,
register, register,
@@ -220,14 +216,22 @@ function UserEditDrawerBody({
<FieldError errors={[errors.nickname]} /> <FieldError errors={[errors.nickname]} />
</FieldContent> </FieldContent>
</Field> </Field>
<Field data-invalid={!!errors.avatar}> <Field className="min-h-32" data-invalid={!!errors.avatar}>
<FieldLabel htmlFor="user-avatar">{t("user.avatar")}</FieldLabel> <FieldLabel>{t("user.avatar")}</FieldLabel>
<FieldContent> <FieldContent>
<Input <Controller
id="user-avatar" control={control}
placeholder={t("user.avatarPlaceholder")} name="avatar"
aria-invalid={!!errors.avatar} render={({ field }) => (
{...register("avatar")} <ImageInput
value={field.value}
onChange={field.onChange}
disabled={saving || loading}
prefix="avatar"
placeholder={t("user.avatarPlaceholder")}
className="size-16 rounded-full"
/>
)}
/> />
<FieldError errors={[errors.avatar]} /> <FieldError errors={[errors.avatar]} />
</FieldContent> </FieldContent>
+3 -2
View File
@@ -81,7 +81,7 @@ export function ImageInput({
const isDisabled = disabled || uploading const isDisabled = disabled || uploading
return ( return (
<div className={cn("relative", className)}> <div className="relative w-fit">
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
@@ -96,7 +96,8 @@ export function ImageInput({
"group relative flex size-24 cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 border-dashed border-input bg-muted transition-colors", "group relative flex size-24 cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 border-dashed border-input bg-muted transition-colors",
"hover:border-primary hover:bg-muted/50", "hover:border-primary hover:bg-muted/50",
"focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none", "focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none",
isDisabled && "cursor-not-allowed opacity-50" isDisabled && "cursor-not-allowed opacity-50",
className
)} )}
tabIndex={isDisabled ? -1 : 0} tabIndex={isDisabled ? -1 : 0}
onKeyDown={(e) => { onKeyDown={(e) => {
+4 -4
View File
@@ -926,7 +926,7 @@
"emptyRows": "No matching users", "emptyRows": "No matching users",
"nicknameRequired": "Enter a nickname.", "nicknameRequired": "Enter a nickname.",
"usernameRequired": "Enter a username.", "usernameRequired": "Enter a username.",
"avatarInvalid": "Avatar URL must start with http or https.", "avatarInvalid": "Invalid avatar.",
"mobileInvalid": "Enter a valid phone number.", "mobileInvalid": "Enter a valid phone number.",
"emailInvalid": "Enter a valid email address.", "emailInvalid": "Enter a valid email address.",
"editTitle": "Edit User", "editTitle": "Edit User",
@@ -934,8 +934,8 @@
"loading": "Loading...", "loading": "Loading...",
"nickname": "Nickname", "nickname": "Nickname",
"nicknamePlaceholder": "Enter nickname", "nicknamePlaceholder": "Enter nickname",
"avatar": "Avatar URL", "avatar": "Avatar",
"avatarPlaceholder": "Enter avatar URL", "avatarPlaceholder": "Upload avatar",
"mobile": "Mobile", "mobile": "Mobile",
"mobilePlaceholder": "Enter mobile number", "mobilePlaceholder": "Enter mobile number",
"email": "Email", "email": "Email",
@@ -948,7 +948,7 @@
"username": "Username", "username": "Username",
"usernamePlaceholder": "Login name, required", "usernamePlaceholder": "Login name, required",
"nicknameCreatePlaceholder": "Optional, defaults to username", "nicknameCreatePlaceholder": "Optional, defaults to username",
"avatarCreatePlaceholder": "Optional, http(s) URL", "avatarCreatePlaceholder": "Upload avatar",
"optional": "Optional", "optional": "Optional",
"remark": "Notes", "remark": "Notes",
"rolesOptional": "Roles (optional)", "rolesOptional": "Roles (optional)",
+4 -4
View File
@@ -926,7 +926,7 @@
"emptyRows": "没有匹配的用户数据", "emptyRows": "没有匹配的用户数据",
"nicknameRequired": "昵称不能为空", "nicknameRequired": "昵称不能为空",
"usernameRequired": "用户名不能为空", "usernameRequired": "用户名不能为空",
"avatarInvalid": "头像地址必须是 http 或 https 链接", "avatarInvalid": "头像不合法",
"mobileInvalid": "手机号格式不正确", "mobileInvalid": "手机号格式不正确",
"emailInvalid": "邮箱格式不正确", "emailInvalid": "邮箱格式不正确",
"editTitle": "修改用户", "editTitle": "修改用户",
@@ -934,8 +934,8 @@
"loading": "加载中...", "loading": "加载中...",
"nickname": "昵称", "nickname": "昵称",
"nicknamePlaceholder": "请输入昵称", "nicknamePlaceholder": "请输入昵称",
"avatar": "头像地址", "avatar": "头像",
"avatarPlaceholder": "请输入头像 URL", "avatarPlaceholder": "上传头像",
"mobile": "手机号", "mobile": "手机号",
"mobilePlaceholder": "请输入手机号", "mobilePlaceholder": "请输入手机号",
"email": "邮箱", "email": "邮箱",
@@ -948,7 +948,7 @@
"username": "用户名", "username": "用户名",
"usernamePlaceholder": "登录名,必填", "usernamePlaceholder": "登录名,必填",
"nicknameCreatePlaceholder": "可选,默认同用户名", "nicknameCreatePlaceholder": "可选,默认同用户名",
"avatarCreatePlaceholder": "可选,http(s) 链接", "avatarCreatePlaceholder": "上传头像",
"optional": "可选", "optional": "可选",
"remark": "备注", "remark": "备注",
"rolesOptional": "角色(可选)", "rolesOptional": "角色(可选)",