diff --git a/client/cms/package.json b/client/cms/package.json index 56e9ac3..34d2284 100644 --- a/client/cms/package.json +++ b/client/cms/package.json @@ -56,6 +56,7 @@ "culori": "^4.0.2", "dayjs": "^1.11.19", "immer": "^11.1.0", + "input-otp": "^1.4.2", "lodash-es": "^4.17.22", "lucide-react": "^0.562.0", "next-themes": "^0.4.6", diff --git a/client/cms/pnpm-lock.yaml b/client/cms/pnpm-lock.yaml index 09700eb..5d51856 100644 --- a/client/cms/pnpm-lock.yaml +++ b/client/cms/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: immer: specifier: ^11.1.0 version: 11.1.3 + input-otp: + specifier: ^1.4.2 + version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) lodash-es: specifier: ^4.17.22 version: 4.17.22 @@ -3931,6 +3934,12 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + input-otp@1.4.2: + resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -9417,6 +9426,11 @@ snapshots: inline-style-parser@0.2.7: {} + input-otp@1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + internmap@2.0.3: {} is-alphabetical@2.0.1: {} diff --git a/client/cms/src/components/checkin/checkin-scanner.dialog.view.tsx b/client/cms/src/components/checkin/checkin-scanner.dialog.view.tsx index fcdad1a..43db4db 100644 --- a/client/cms/src/components/checkin/checkin-scanner.dialog.view.tsx +++ b/client/cms/src/components/checkin/checkin-scanner.dialog.view.tsx @@ -1,11 +1,15 @@ import { Scanner } from '@yudiel/react-qr-scanner'; +import { REGEXP_ONLY_DIGITS } from 'input-otp'; import { DialogContent, DialogHeader, DialogTitle } from '../ui/dialog'; +import { InputOTP, InputOTPGroup, InputOTPSlot } from '../ui/input-otp'; export function CheckinScannerDialogView({ onScan }: { onScan: (value: string) => void }) { return ( 扫描签到码 + + { if (result.length > 0) { @@ -14,7 +18,33 @@ export function CheckinScannerDialogView({ onScan }: { onScan: (value: string) = }} onError={(error) => { throw error; }} /> - + + + + + + + + 手动输入 + + + + + onScan(value)} + > + + + + + + + + + + ); } diff --git a/client/cms/src/components/ui/input-otp.tsx b/client/cms/src/components/ui/input-otp.tsx new file mode 100644 index 0000000..3101752 --- /dev/null +++ b/client/cms/src/components/ui/input-otp.tsx @@ -0,0 +1,75 @@ +import * as React from "react" +import { OTPInput, OTPInputContext } from "input-otp" +import { MinusIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function InputOTP({ + className, + containerClassName, + ...props +}: React.ComponentProps & { + containerClassName?: string +}) { + return ( + + ) +} + +function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( + + ) +} + +function InputOTPSlot({ + index, + className, + ...props +}: React.ComponentProps<"div"> & { + index: number +}) { + const inputOTPContext = React.useContext(OTPInputContext) + const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {} + + return ( + + {char} + {hasFakeCaret && ( + + + + )} + + ) +} + +function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) { + return ( + + + + ) +} + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }