工作笔记
本周设计回顾
下一版组件清单
'use client'
import { useState } from 'react'
import { Plus } from 'lucide-react'
import {
Button,
Card,
Dialog,
FloatButton,
FormField,
Heading,
Input,
Stack,
Text,
} from '@hina-ui/react'
export default function Demo() {
const [open, setOpen] = useState(false)
const [title, setTitle] = useState('')
const [notes, setNotes] = useState(['本周设计回顾', '下一版组件清单'])
function create() {
if (!title.trim()) return
setNotes([...notes, title.trim()])
setTitle('')
setOpen(false)
}
return (
<Card className="relative min-h-72 w-full max-w-md pb-24">
<Stack gap="lg">
<Heading level={3} size="base">
工作笔记
</Heading>
<Stack gap="sm">
{notes.map((note, index) => (
<Text key={index} className="border-line border-b pb-3">
{note}
</Text>
))}
</Stack>
</Stack>
<FloatButton position="absolute" label="新建笔记" onClick={() => setOpen(true)}>
<Plus />
</FloatButton>
<Dialog
open={open}
onOpenChange={setOpen}
title="新建笔记"
description="为新的笔记填写标题。"
renderContent={() => (
<FormField label="笔记标题">
<Input
value={title}
onValueChange={setTitle}
placeholder="例如:交互细节"
onKeyDown={event => {
if (event.key !== 'Enter') return
event.preventDefault()
create()
}}
/>
</FormField>
)}
renderFooter={() => (
<>
<Button variant="soft" tone="neutral" onClick={() => setOpen(false)}>
取消
</Button>
<Button disabled={!title.trim()} onClick={create}>
创建
</Button>
</>
)}
/>
</Card>
)
}
用法
import { FloatButton } from '@hina-ui/react'
默认固定在视口的逻辑右下角。通过 label 提供操作名称,children 放图标。浮动按钮适合新建、帮助这类跨内容区域的常用操作;行内操作继续使用 Button 或 IconButton。
<FloatButton label="新建笔记" onClick={createNote}>
<Plus />
</FloatButton>
整页使用时放在应用壳外层,避免祖先的 transform 改变固定定位的参照。组件不通过 portal 渲染到 body,保留当前位置的主题、方向和组件上下文。上面的演示使用 position="absolute",将按钮限制在预览面板内。
示例
外观
支持实底、浅底和描边,shape="square" 使用圆角方形。图标按钮沿用现有 Tooltip;应用需提供 TooltipProvider,AppShell 已包含它。label 始终作为无障碍名称,不依赖 Tooltip。
悬停或键盘聚焦可以查看操作名称
'use client'
import { useState } from 'react'
import { HelpCircle, Pencil, Plus } from 'lucide-react'
import { FloatButton, Flex, Stack, Text } from '@hina-ui/react'
export default function Demo() {
const [result, setResult] = useState('悬停或键盘聚焦可以查看操作名称')
return (
<Stack align="center" gap="lg">
<Flex wrap justify="center" gap="lg">
<FloatButton
position="static"
label="新建项目"
onClick={() => setResult('已选择:新建项目')}
>
<Plus />
</FloatButton>
<FloatButton
position="static"
label="编辑项目"
variant="soft"
shape="square"
onClick={() => setResult('已选择:编辑项目')}
>
<Pencil />
</FloatButton>
<FloatButton
position="static"
label="帮助中心"
variant="outline"
tone="neutral"
onClick={() => setResult('已选择:帮助中心')}
>
<HelpCircle />
</FloatButton>
</Flex>
<Text role="status" size="sm" tone="muted">
{result}
</Text>
</Stack>
)
}
尺寸与文字
sm、md、lg 分别对应 40、48、56px 的默认高度。extended 将 label 显示在图标旁,此时不会重复显示 Tooltip。长文案在可用宽度内截断。
文字按钮使用同一高度,宽度随名称展开
'use client'
import { useState } from 'react'
import { Plus } from 'lucide-react'
import { FloatButton, Flex, Stack, Text } from '@hina-ui/react'
export default function Demo() {
const [result, setResult] = useState('文字按钮使用同一高度,宽度随名称展开')
return (
<Stack align="center" gap="lg">
<Flex wrap align="center" justify="center" gap="lg">
{(['sm', 'md', 'lg'] as const).map(size => (
<FloatButton
key={size}
position="static"
size={size}
label={`新建项目(${size})`}
onClick={() => setResult(`已选择:${size}`)}
>
<Plus />
</FloatButton>
))}
<FloatButton
position="static"
extended
label="新建项目"
onClick={() => setResult('已选择:新建项目')}
>
<Plus />
</FloatButton>
</Flex>
<Text role="status" size="sm" tone="muted">
{result}
</Text>
</Stack>
)
}
定位与方向
placement 使用逻辑方向,start / end 随 RTL 切换。absolute 相对最近的定位祖先,static 参与正常布局,适合与 Stack 组合成一组操作。offset 控制边距;固定定位时还会避开设备安全区。
start / end 随书写方向切换
'use client'
import { useState } from 'react'
import { Plus } from 'lucide-react'
import { Card, Center, FloatButton, FormField, Stack, Switch, Text } from '@hina-ui/react'
const placements = ['top-start', 'top-end', 'bottom-start', 'bottom-end'] as const
export default function Demo() {
const [rtl, setRtl] = useState(false)
const [result, setResult] = useState('start / end 随书写方向切换')
return (
<Stack className="w-full max-w-md">
<FormField label="从右向左" orientation="horizontal">
<Switch checked={rtl} onCheckedChange={setRtl} />
</FormField>
<Card dir={rtl ? 'rtl' : 'ltr'} className="relative h-64" padded={false}>
<Center className="h-full px-16">
<Text role="status" size="sm" tone="muted" className="text-center">
{result}
</Text>
</Center>
{placements.map(placement => (
<FloatButton
key={placement}
position="absolute"
placement={placement}
label={placement}
offset={16}
variant="soft"
size="sm"
onClick={() => setResult(placement)}
>
<Plus />
</FloatButton>
))}
</Card>
</Stack>
)
}
状态与显隐
visible 控制出现和退场,退场期间按钮不能交互。loading 保留按钮尺寸并阻止重复操作,disabled 禁用按钮。
显示和隐藏带有过渡
'use client'
import { useState } from 'react'
import { RefreshCw } from 'lucide-react'
import { Center, FloatButton, FormField, Stack, Switch, Text } from '@hina-ui/react'
export default function Demo() {
const [visible, setVisible] = useState(true)
const [loading, setLoading] = useState(false)
const [disabled, setDisabled] = useState(false)
const [result, setResult] = useState('显示和隐藏带有过渡')
return (
<Stack className="w-full max-w-xs">
<FormField label="显示按钮" orientation="horizontal">
<Switch checked={visible} onCheckedChange={setVisible} />
</FormField>
<FormField label="加载中" orientation="horizontal">
<Switch checked={loading} onCheckedChange={setLoading} />
</FormField>
<FormField label="禁用" orientation="horizontal">
<Switch checked={disabled} onCheckedChange={setDisabled} />
</FormField>
<Center className="h-24">
<FloatButton
position="static"
label="同步资料"
visible={visible}
loading={loading}
disabled={disabled}
onClick={() => setResult('已执行:同步资料')}
>
<RefreshCw />
</FloatButton>
</Center>
<Text role="status" size="sm" tone="muted" className="text-center">
{result}
</Text>
</Stack>
)
}
应用内导航
FloatButton 的属性类型只包含按钮属性,没有 href。需要跳转到其他页面时,在客户端组件中用 next/navigation 的 useRouter,在 onClick 中调用 router.push。
'use client'
import { useRouter } from 'next/navigation'
import { BookOpen } from 'lucide-react'
import { FloatButton } from '@hina-ui/react'
export function DocsShortcut() {
const router = useRouter()
return (
<FloatButton label="组件文档" onClick={() => router.push('/components')}>
<BookOpen />
</FloatButton>
)
}
API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | 必填 | 操作名称,同时用于无障碍和 Tooltip |
visible | boolean | true | 显示按钮,变化时播放过渡 |
position | 'fixed' | 'absolute' | 'static' | 'fixed' | 定位方式 |
placement | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'bottom-end' | 定位角落,静态布局时无效 |
offset | number | string | 6 × spacing | 边距,数字单位为 px;固定定位取边距与安全区中的较大值 |
size | 'sm' | 'md' | 'lg' | 'md' | 按钮尺寸 |
shape | 'circle' | 'square' | 'circle' | 圆形或圆角方形 |
extended | boolean | false | 显示文字标签 |
variant | 'solid' | 'soft' | 'outline' | 'solid' | 按钮外观 |
tone | 'accent' | 'neutral' | 'danger' | 'accent' | 按钮色调 |
tooltip | boolean | true | 图标形态下显示 Tooltip |
tooltipSide | 'top' | 'right' | 'bottom' | 'left' | 'top' | Tooltip 首选方向 |
loading | boolean | false | 加载中并禁止操作 |
disabled | boolean | false | 禁用操作 |
ripple | boolean | true | 涟漪反馈 |
as | string | Component | 'button' | 实际操作元素或组件 |
type | 'button' | 'submit' | 'reset' | 'button' | 按钮类型 |
className | string | — | 按钮类名 |
style | CSSProperties | — | 按钮样式 |
原生属性和事件透传到按钮,例如 id、form 和 onClick。
内容属性
| 属性 | 说明 |
|---|---|
children | 操作图标; extended 时放在文字前面 |
Ref
| 名称 | 类型 | 说明 |
|---|---|---|
element | HTMLElement | undefined | 当前操作节点 |
focus | () => void | 聚焦可用的按钮 |