InputGroup 输入框组
将相关输入控件与前后缀元素组合,以增强表单字段
用法
import { InputGroup } from '@lenso/ui';此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";
import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
// Adapted from HeroUI v3.2.6, Apache-2.0.const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});
export function Default() { return ( <TextField xstyle={styles.field} name="email"> <Label>Email address</Label> <InputGroup> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" /> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
组件结构
import {InputGroup, TextField, Label} from '@lenso/ui';
export default () => ( <TextField> <Label /> <InputGroup> <InputGroup.Prefix /> <InputGroup.Input /> {/* Or use InputGroup.TextArea for multiline input */} <InputGroup.Suffix /> </InputGroup> </TextField>)InputGroup 使用可选的前缀与后缀包裹输入框,形成视觉上统一的组合。通常放在 TextField 内,用于在输入前后添加图标、文字、按钮等元素。单行输入请使用 InputGroup.Input,多行输入请使用 InputGroup.TextArea。
示例
变体
InputGroup 组件支持两种视觉变体:
primary(默认)- 标准样式带阴影,适用于大多数场景secondary- 低强调变体无阴影,适用于 Surface 组件内
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ stack: { display: "flex", flexDirection: "column", gap: 16 }, field: { width: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function Variants() { return ( <div {...stylex.props(styles.stack)}> <TextField xstyle={styles.field} name="primary"> <Label>Primary variant</Label> <InputGroup variant="primary"> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input placeholder="[email protected]" /> </InputGroup> </TextField> <TextField xstyle={styles.field} name="secondary"> <Label>Secondary variant</Label> <InputGroup variant="secondary"> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input placeholder="[email protected]" /> </InputGroup> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
表面样式
在 Surface 内使用时,请使用 variant="secondary" 以应用适合 Surface 背景的低强调变体。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, Surface, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ surface: { borderRadius: 16, padding: 24 }, field: { width: "100%", maxWidth: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function OnSurface() { return ( <Surface xstyle={styles.surface}> <TextField xstyle={styles.field} name="email"> <Label>Email address</Label> <InputGroup variant="secondary"> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" /> </InputGroup> <Description>We'll never share this with anyone else</Description> </TextField> </Surface> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
后缀加载状态
在后缀显示加载 spinner 以表示正在处理。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Spinner, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, spinner: { width: 16, height: 16 },});export function WithLoadingSuffix() { return ( <TextField xstyle={styles.field} name="status"> <InputGroup> <InputGroup.Input xstyle={styles.field} defaultValue="Sending..." /> <InputGroup.Suffix> <Spinner xstyle={styles.spinner} /> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
必填字段
InputGroup 会遵循父级 TextField 的必填状态。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ stack: { display: "flex", flexDirection: "column", gap: 16 }, field: { width: "100%", maxWidth: 280 }, price: { width: "100%", maxWidth: 200 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function Required() { return ( <div {...stylex.props(styles.stack)}> <TextField xstyle={styles.field} name="email"> <Label required>Email address</Label> <InputGroup> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input required xstyle={styles.field} placeholder="[email protected]" /> </InputGroup> </TextField> <TextField xstyle={styles.field} name="price"> <Label required>Set a price</Label> <InputGroup> <InputGroup.Prefix>$</InputGroup.Prefix> <InputGroup.Input required xstyle={styles.price} placeholder="0" type="number" /> <InputGroup.Suffix>USD</InputGroup.Suffix> </InputGroup> <Description>What customers would pay</Description> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
禁用状态
InputGroup 会遵循父级 TextField 的禁用状态。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ stack: { display: "flex", flexDirection: "column", gap: 16 }, field: { width: "100%", maxWidth: 280 }, price: { width: "100%", maxWidth: 200 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function Disabled() { return ( <div {...stylex.props(styles.stack)}> <TextField disabled xstyle={styles.field} name="email"> <Label>Email address</Label> <InputGroup> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} defaultValue="[email protected]" /> </InputGroup> </TextField> <TextField disabled xstyle={styles.field} name="price"> <Label>Set a price</Label> <InputGroup> <InputGroup.Prefix>$</InputGroup.Prefix> <InputGroup.Input xstyle={styles.price} defaultValue="10" type="number" /> <InputGroup.Suffix>USD</InputGroup.Suffix> </InputGroup> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
宽度充满
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope, Eye } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ stack: { width: 400, display: "flex", flexDirection: "column", gap: 16 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function FullWidth() { return ( <div {...stylex.props(styles.stack)}> <TextField fullWidth name="email"> <Label>Email address</Label> <InputGroup fullWidth> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input placeholder="[email protected]" /> </InputGroup> </TextField> <TextField fullWidth name="password"> <Label>Password</Label> <InputGroup fullWidth> <InputGroup.Input placeholder="Enter password" type="password" /> <InputGroup.Suffix> <Eye aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Suffix> </InputGroup> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
文字前缀
使用文字作为前缀,例如货币符号或协议前缀。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 } });export function WithTextPrefix() { return ( <TextField xstyle={styles.field} name="website"> <Label>Website</Label> <InputGroup> <InputGroup.Prefix>https://</InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" /> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
文字后缀
使用文字作为后缀,例如域名后缀或单位。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 } });export function WithTextSuffix() { return ( <TextField xstyle={styles.field} name="website"> <Label>Website</Label> <InputGroup> <InputGroup.Input xstyle={styles.field} defaultValue="heroui" /> <InputGroup.Suffix>.com</InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
图标前缀与文字后缀
组合图标前缀与文字后缀。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Globe } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithIconPrefixAndTextSuffix() { return ( <TextField xstyle={styles.field} name="website"> <Label>Website</Label> <InputGroup> <InputGroup.Prefix> <Globe aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} defaultValue="heroui" /> <InputGroup.Suffix>.com</InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
复制按钮后缀
在后缀中加入交互按钮,例如复制按钮。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Copy } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, suffix: { paddingInlineEnd: 0 }, icon: { width: 16, height: 16 },});export function WithCopySuffix() { return ( <TextField xstyle={styles.field} name="website"> <Label>Website</Label> <InputGroup> <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" /> <InputGroup.Suffix xstyle={styles.suffix}> <Button isIconOnly aria-label="Copy" size="sm" variant="ghost"> <Copy aria-hidden="true" {...stylex.props(styles.icon)} /> </Button> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
图标前缀与复制按钮
组合图标前缀与交互式后缀按钮。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Copy, Globe } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, suffix: { paddingInlineEnd: 0 }, icon: { width: 16, height: 16 }, muted: { color: "var(--muted)" },});export function WithIconPrefixAndCopySuffix() { return ( <TextField xstyle={styles.field} name="website"> <Label>Website</Label> <InputGroup> <InputGroup.Prefix> <Globe aria-hidden="true" {...stylex.props(styles.icon, styles.muted)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" /> <InputGroup.Suffix xstyle={styles.suffix}> <Button isIconOnly aria-label="Copy" size="sm" variant="ghost"> <Copy aria-hidden="true" {...stylex.props(styles.icon)} /> </Button> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
密码可见性切换
在后缀中使用按钮切换密码可见性。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Eye, EyeSlash } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useState } from "react";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, suffix: { paddingInlineEnd: 0 }, icon: { width: 16, height: 16 },});export function PasswordWithToggle() { const [isVisible, setIsVisible] = useState(false); return ( <TextField xstyle={styles.field} name="password"> <Label>Password</Label> <InputGroup> <InputGroup.Input xstyle={styles.field} type={isVisible ? "text" : "password"} value={isVisible ? "87$2h.3diua" : "••••••••"} readOnly /> <InputGroup.Suffix xstyle={styles.suffix}> <Button isIconOnly aria-label={isVisible ? "Hide password" : "Show password"} size="sm" variant="ghost" onClick={() => setIsVisible(!isVisible)} > {isVisible ? ( <Eye aria-hidden="true" {...stylex.props(styles.icon)} /> ) : ( <EyeSlash aria-hidden="true" {...stylex.props(styles.icon)} /> )} </Button> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
键盘快捷键
使用 Kbd 组件展示键盘快捷键。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Kbd, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, suffix: { paddingInlineEnd: 8 },});export function WithKeyboardShortcut() { return ( <TextField xstyle={styles.field} name="command"> <InputGroup> <InputGroup.Input aria-label="Command" xstyle={styles.field} placeholder="Command" /> <InputGroup.Suffix xstyle={styles.suffix}> <Kbd> <Kbd.Abbr keyValue="command" /> <Kbd.Content>K</Kbd.Content> </Kbd> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
徽章后缀
在后缀中加入徽章或 chip,用于展示状态或标签。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Chip, InputGroup, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, suffix: { paddingInlineEnd: 8 },});export function WithBadgeSuffix() { return ( <TextField xstyle={styles.field} name="email"> <InputGroup> <InputGroup.Input aria-label="Email address" xstyle={styles.field} placeholder="Email address" /> <InputGroup.Suffix xstyle={styles.suffix}> <Chip color="accent" size="md" variant="soft"> Pro </Chip> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
表单校验
InputGroup 会自动反映父级 TextField 的无效状态。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { FieldError, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ stack: { display: "flex", flexDirection: "column", gap: 16 }, field: { width: "100%", maxWidth: 280 }, price: { width: "100%", maxWidth: 200 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function Invalid() { return ( <div {...stylex.props(styles.stack)}> <TextField invalid xstyle={styles.field} name="email"> <Label required>Email address</Label> <InputGroup> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input required xstyle={styles.field} placeholder="[email protected]" /> </InputGroup> <FieldError match>Please enter a valid email address</FieldError> </TextField> <TextField invalid xstyle={styles.field} name="price"> <Label required>Set a price</Label> <InputGroup> <InputGroup.Prefix>$</InputGroup.Prefix> <InputGroup.Input required xstyle={styles.price} placeholder="0" type="number" /> <InputGroup.Suffix>USD</InputGroup.Suffix> </InputGroup> <FieldError match>Price must be greater than 0</FieldError> </TextField> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
前缀图标
在输入框前添加图标。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithPrefixIcon() { return ( <TextField xstyle={styles.field} name="email"> <Label>Email address</Label> <InputGroup> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" /> </InputGroup> <Description>We'll never share this with anyone else</Description> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
后缀图标
在输入框后添加图标。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithSuffixIcon() { return ( <TextField xstyle={styles.field} name="email"> <Label>Email address</Label> <InputGroup> <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" /> <InputGroup.Suffix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Suffix> </InputGroup> <Description>We don't send spam</Description> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
前缀与后缀
同时组合前缀与后缀。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 }, input: { width: "100%", maxWidth: 200 },});export function WithPrefixAndSuffix() { return ( <TextField xstyle={styles.field} name="price"> <Label>Set a price</Label> <InputGroup> <InputGroup.Prefix>$</InputGroup.Prefix> <InputGroup.Input xstyle={styles.input} defaultValue="10" type="number" /> <InputGroup.Suffix>USD</InputGroup.Suffix> </InputGroup> <Description>What customers would pay</Description> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
文本域
多行输入请使用 InputGroup.TextArea,并搭配前缀与后缀。当存在 textarea 时,容器高度会自动适应内容,并将前缀/后缀与顶部对齐。
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { ArrowUp, At, Microphone, PlugConnection, Plus } from "@gravity-ui/icons";import { Button, InputGroup, Kbd, Spinner, TextField, Tooltip } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useState } from "react";const styles = stylex.create({ field: { display: "flex", flexDirection: "column", width: { default: 384, "@media (min-width: 640px)": 512 }, }, group: { display: "flex", flexDirection: "column", gap: 8, borderRadius: 24, paddingBlock: 8 }, prefix: { paddingInline: 12, paddingBlock: 0 }, input: { width: "100%", resize: "none", paddingInline: 14, paddingBlock: 0 }, suffix: { display: "flex", width: "100%", alignItems: "center", gap: 6, paddingInline: 12, paddingBlock: 0, }, actions: { marginInlineStart: "auto", display: "flex", alignItems: "center", gap: 6 }, tooltip: { display: "flex", alignItems: "center", gap: 4 }, hint: { fontSize: 12, lineHeight: "16px" }, key: { height: 16, borderRadius: "var(--radius-sm)", paddingInline: 4 },});export function WithTextArea() { const [value, setValue] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = () => { if (!value.trim()) return; setIsSubmitting(true); setTimeout(() => { setIsSubmitting(false); setValue(""); }, 1000); }; return ( <TextField fullWidth xstyle={styles.field} name="prompt"> <InputGroup fullWidth xstyle={styles.group}> <InputGroup.Prefix xstyle={styles.prefix}> <Button aria-label="Add context" size="sm" variant="outline"> <At aria-hidden="true" /> Add Context </Button> </InputGroup.Prefix> <InputGroup.TextArea aria-label="Prompt input" xstyle={styles.input} placeholder="Assign tasks or ask anything..." rows={5} value={value} onChange={(event) => setValue(event.target.value)} /> <InputGroup.Suffix xstyle={styles.suffix}> <Tooltip> <Tooltip.Trigger delay={0} render={<Button isIconOnly aria-label="Attach file" size="sm" variant="tertiary" />} > <Plus aria-hidden="true" /> </Tooltip.Trigger> <Tooltip.Portal> <Tooltip.Positioner> <Tooltip.Popup> <p {...stylex.props(styles.hint)}>Add a files and more</p> </Tooltip.Popup> </Tooltip.Positioner> </Tooltip.Portal> </Tooltip> <Tooltip> <Tooltip.Trigger delay={0} render={<Button isIconOnly aria-label="Connect Apps" size="sm" variant="tertiary" />} > <PlugConnection aria-hidden="true" /> </Tooltip.Trigger> <Tooltip.Portal> <Tooltip.Positioner> <Tooltip.Popup> <p {...stylex.props(styles.hint)}>Connect apps</p> </Tooltip.Popup> </Tooltip.Positioner> </Tooltip.Portal> </Tooltip> <div {...stylex.props(styles.actions)}> <Tooltip> <Tooltip.Trigger delay={0} render={<Button isIconOnly aria-label="Voice input" size="sm" variant="ghost" />} > <Microphone aria-hidden="true" /> </Tooltip.Trigger> <Tooltip.Portal> <Tooltip.Positioner> <Tooltip.Popup> <p {...stylex.props(styles.hint)}>Voice input</p> </Tooltip.Popup> </Tooltip.Positioner> </Tooltip.Portal> </Tooltip> <Tooltip> <Tooltip.Trigger delay={0} render={ <Button isIconOnly aria-label="Send prompt" disabled={!value.trim()} isLoading={isSubmitting} onClick={handleSubmit} /> } > {isSubmitting ? ( <Spinner color="current" size="sm" /> ) : ( <ArrowUp aria-hidden="true" /> )} </Tooltip.Trigger> <Tooltip.Portal> <Tooltip.Positioner> <Tooltip.Popup xstyle={styles.tooltip}> <p {...stylex.props(styles.hint)}>Send</p> <Kbd xstyle={styles.key}> <Kbd.Abbr keyValue="enter" /> </Kbd> </Tooltip.Popup> </Tooltip.Positioner> </Tooltip.Portal> </Tooltip> </div> </InputGroup.Suffix> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
用法示例
import {InputGroup, TextField, Label, Button} from '@lenso/ui';import {Icon} from '@iconify/react';
function Example() { return ( <TextField> <Label>Email</Label> <InputGroup> <InputGroup.Prefix> <Icon icon="gravity-ui:envelope" /> </InputGroup.Prefix> <InputGroup.Input placeholder="[email protected]" /> <InputGroup.Suffix> <Button isIconOnly aria-label="Confirm" size="sm" variant="ghost"> <Icon icon="gravity-ui:check" /> </Button> </InputGroup.Suffix> </InputGroup> </TextField> );}文本域用法示例
import {Envelope} from "@gravity-ui/icons";import {Description, FieldError, InputGroup, Label, TextField} from "@lenso/ui";import {useState} from "react";
function TextAreaExample() { const [feedback, setFeedback] = useState("");
return ( <TextField fullWidth isInvalid={feedback.length > 500} name="feedback" onChange={setFeedback}> <Label>Your Feedback</Label> <InputGroup fullWidth> <InputGroup.Prefix> <Envelope className="size-4 text-muted" /> </InputGroup.Prefix> <InputGroup.TextArea className="resize-none" placeholder="Share your thoughts, suggestions, or issues..." rows={5} value={feedback} /> </InputGroup> <Description className="flex w-full items-center justify-between px-1"> <span>Maximum 500 characters.</span> <span className="ml-auto">{feedback.length}/500</span> </Description> <FieldError>Feedback must be less than 500 characters</FieldError> </TextField> );}自定义样式
Tailwind CSS
此预览复用英文版适配,不代表中文源示例已完成本地实现。
"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 320 }, group: { borderRadius: 12, borderWidth: 1, borderStyle: "solid", borderColor: "color-mix(in oklab, var(--border) 80%, transparent)", backgroundColor: "var(--default)", boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", }, icon: { width: 16, height: 16, color: "var(--muted)" },});export function CustomStyles() { return ( <TextField xstyle={styles.field} name="email"> <Label>Work email</Label> <InputGroup xstyle={styles.group}> <InputGroup.Prefix> <Envelope aria-hidden="true" {...stylex.props(styles.icon)} /> </InputGroup.Prefix> <InputGroup.Input placeholder="[email protected]" /> </InputGroup> </TextField> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
全局 CSS
InputGroup 使用可自定义的 CSS 类。覆盖组件类以匹配设计系统。
@layer components { .input-group { @apply bg-field text-field-foreground shadow-field rounded-field inline-flex min-h-9 items-center overflow-hidden border text-sm outline-none; }
.input-group__input { @apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none; }
.input-group__prefix { @apply text-field-placeholder rounded-l-field flex h-full items-center justify-center rounded-r-none bg-transparent px-3; }
.input-group__suffix { @apply text-field-placeholder rounded-r-field flex h-full items-center justify-center rounded-l-none bg-transparent px-3; }
/* Secondary variant */ .input-group--secondary { @apply shadow-none; background-color: var(--color-default); }}样式参考
HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。
CSS 类
基础类 [!toc]
.input-group– 根容器:带边框、背景与 flex 布局。默认min-h-9与items-center;存在 textarea 时切换为items-start.input-group__input– 透明背景、无边框的输入元素。textarea 也使用该基础类.input-group__prefix– 左侧圆角的前缀容器。与 textarea 搭配时与顶部对齐.input-group__suffix– 右侧圆角的后缀容器。与 textarea 搭配时与顶部对齐
变体类 [!toc]
.input-group--primary– 带阴影的主变体(默认).input-group--secondary– 无阴影的次变体,适用于 Surface 内
Note: 使用 InputGroup.TextArea 时,容器从 items-center 切换为 items-start,并使用 height: auto 替代固定高度。前缀与后缀与顶部对齐,并增加内边距以匹配 textarea 的垂直内边距。textarea 使用相同的 .input-group__input 基础类,并通过 [data-slot="input-group-textarea"] 选择器应用 textarea 专用样式(最小高度与纵向 resize)。
交互状态
InputGroup 会根据状态自动管理以下 data 属性:
- Hover:
[data-hovered]- 悬停在组合上时应用 - Focus Within:
[data-focus-within]- 输入聚焦时应用 - Invalid:
[data-invalid]- 父级 TextField 无效时应用 - Disabled:
[data-disabled]或[aria-disabled]- 父级 TextField 禁用时应用
API 参考
InputGroup
InputGroup 继承 React Aria Group 组件的全部 props。
Base Props
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
children | React.ReactNode | (values: GroupRenderProps) => React.ReactNode | - | 子组件(Input、TextArea、Prefix、Suffix)或 render 函数 |
className | string | (values: GroupRenderProps) => string | - | CSS 类,支持 render props |
style | React.CSSProperties | (values: GroupRenderProps) => React.CSSProperties | - | 行内样式,支持 render props |
fullWidth | boolean | false | 输入组是否占满容器宽度 |
id | string | - | 元素唯一标识符 |
Variant Props
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
variant | "primary" | "secondary" | "primary" | 视觉变体。primary 为默认带阴影样式;secondary 为低强调无阴影,适用于 Surface 内 |
Accessibility Props
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
aria-label | string | - | 无可见标签时的无障碍标签 |
aria-labelledby | string | - | 标注该组的元素 ID |
aria-describedby | string | - | 描述该组的元素 ID |
aria-details | string | - | 包含更多详情的元素 ID |
role | 'group' | 'region' | 'presentation' | 'group' | 分组的无障碍角色。重要内容用 region,纯视觉分组用 presentation |
Composition Components
InputGroup 与以下子组件配合使用:
- InputGroup.Root - 根容器(也可写作
InputGroup) - InputGroup.Input - 单行输入元素组件
- InputGroup.TextArea - 多行 textarea 元素组件
- InputGroup.Prefix - 前缀容器组件
- InputGroup.Suffix - 后缀容器组件
InputGroup.Input
InputGroup.Input 继承 React Aria Input 组件的全部 props。
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | CSS 类 |
variant | "primary" | "secondary" | "primary" | 输入视觉变体 |
type | string | 'text' | 输入类型(text、password、email 等) |
value | string | - | 当前值(受控) |
defaultValue | string | - | 默认值(非受控) |
placeholder | string | - | 占位文本 |
disabled | boolean | - | 是否禁用 |
readOnly | boolean | - | 是否只读 |
InputGroup.TextArea
InputGroup.TextArea 继承 React Aria TextArea 组件的全部 props。
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
className | string | - | CSS 类 |
variant | "primary" | "secondary" | "primary" | textarea 视觉变体 |
value | string | - | 当前值(受控) |
defaultValue | string | - | 默认值(非受控) |
placeholder | string | - | 占位文本 |
rows | number | - | 可见文本行数 |
disabled | boolean | - | 是否禁用 |
readOnly | boolean | - | 是否只读 |
InputGroup.Prefix
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
children | React.ReactNode | - | 前缀内容(图标、文字等) |
className | string | - | CSS 类 |
InputGroup.Suffix
| Prop | 类型 | 默认值 | 描述 |
|---|---|---|---|
children | React.ReactNode | - | 后缀内容(图标、按钮、徽章等) |
className | string | - | CSS 类 |
相关案例
See upstream InputGroup showcases. Product showcases are not part of the local component runtime.