@teamix-evo/ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Mail, Search } from 'lucide-react';
|
|
4
|
+
import { Input } from './input';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Input> = {
|
|
7
|
+
title: '表单与输入 · Form/Input',
|
|
8
|
+
component: Input,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
'输入框 — 文本 / 邮箱 / 密码 / 搜索等单行输入。shadcn 原生 `<input>` 风格 + antd 扩展:`size` 三档、`prefix` / `suffix` 前后缀、`addonBefore` / `addonAfter` 外联组、`clearable` 一键清空、`showCount` + `maxLength` 字数统计。视觉走 OpenTrek semantic tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
size: { control: 'inline-radio', options: ['sm', 'default', 'lg'] },
|
|
20
|
+
type: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['text', 'email', 'password', 'search', 'tel', 'url'],
|
|
23
|
+
},
|
|
24
|
+
clearable: { control: 'boolean' },
|
|
25
|
+
showCount: { control: 'boolean' },
|
|
26
|
+
disabled: { control: 'boolean' },
|
|
27
|
+
placeholder: { control: 'text' },
|
|
28
|
+
maxLength: { control: 'number' },
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
size: 'default',
|
|
32
|
+
type: 'text',
|
|
33
|
+
clearable: false,
|
|
34
|
+
showCount: false,
|
|
35
|
+
disabled: false,
|
|
36
|
+
placeholder: '请输入...',
|
|
37
|
+
},
|
|
38
|
+
decorators: [
|
|
39
|
+
(Story) => (
|
|
40
|
+
<div className="w-72">
|
|
41
|
+
<Story />
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default meta;
|
|
48
|
+
type Story = StoryObj<typeof Input>;
|
|
49
|
+
|
|
50
|
+
export const Playground: Story = {};
|
|
51
|
+
|
|
52
|
+
export const Sizes: Story = {
|
|
53
|
+
parameters: { controls: { disable: true } },
|
|
54
|
+
render: () => (
|
|
55
|
+
<div className="flex flex-col gap-3">
|
|
56
|
+
<Input size="sm" placeholder="Small" />
|
|
57
|
+
<Input size="default" placeholder="Default" />
|
|
58
|
+
<Input size="lg" placeholder="Large" />
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const PrefixSuffix: Story = {
|
|
64
|
+
parameters: { controls: { disable: true } },
|
|
65
|
+
render: () => (
|
|
66
|
+
<div className="flex flex-col gap-3">
|
|
67
|
+
<Input prefix={<Search />} placeholder="搜索..." />
|
|
68
|
+
<Input type="email" suffix={<Mail />} placeholder="you@example.com" />
|
|
69
|
+
<Input prefix="¥" suffix="USD" placeholder="0.00" />
|
|
70
|
+
</div>
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const Addon: Story = {
|
|
75
|
+
parameters: { controls: { disable: true } },
|
|
76
|
+
render: () => (
|
|
77
|
+
<div className="flex flex-col gap-3">
|
|
78
|
+
<Input
|
|
79
|
+
addonBefore="https://"
|
|
80
|
+
defaultValue="teamix-evo"
|
|
81
|
+
addonAfter=".com"
|
|
82
|
+
/>
|
|
83
|
+
<Input addonBefore="@" placeholder="username" />
|
|
84
|
+
<Input addonAfter="kg" defaultValue="42" />
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const Clearable: Story = {
|
|
90
|
+
parameters: { controls: { disable: true } },
|
|
91
|
+
render: () => {
|
|
92
|
+
const [v, setV] = React.useState('Hello');
|
|
93
|
+
return (
|
|
94
|
+
<Input
|
|
95
|
+
clearable
|
|
96
|
+
value={v}
|
|
97
|
+
onChange={(e) => setV(e.target.value)}
|
|
98
|
+
placeholder="可清除..."
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const ShowCount: Story = {
|
|
105
|
+
parameters: { controls: { disable: true } },
|
|
106
|
+
render: () => <Input showCount maxLength={140} placeholder="发布动态" />,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const Disabled: Story = {
|
|
110
|
+
parameters: { controls: { disable: true } },
|
|
111
|
+
render: () => (
|
|
112
|
+
<div className="flex flex-col gap-3">
|
|
113
|
+
<Input disabled placeholder="禁用态" />
|
|
114
|
+
<Input disabled defaultValue="只读内容" />
|
|
115
|
+
</div>
|
|
116
|
+
),
|
|
117
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/utils/cn';
|
|
5
|
+
|
|
6
|
+
export interface InputProps
|
|
7
|
+
extends Omit<
|
|
8
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
9
|
+
'size' | 'prefix' | 'suffix'
|
|
10
|
+
> {
|
|
11
|
+
/** 输入框前置图标 / 文本(antd `prefix` 并集)。 */
|
|
12
|
+
prefix?: React.ReactNode;
|
|
13
|
+
/** 输入框后置图标 / 文本(antd `suffix` 并集)。 */
|
|
14
|
+
suffix?: React.ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* 显示清除按钮,有内容时点击清空(antd `allowClear` 并集)。
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
clearable?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 显示字符计数(antd `showCount` 并集);需配合 `maxLength` 一起使用。
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
showCount?: boolean;
|
|
25
|
+
/** 输入框前置标签(antd `addonBefore` 并集),与外层组合形成 input-group。 */
|
|
26
|
+
addonBefore?: React.ReactNode;
|
|
27
|
+
/** 输入框后置标签(antd `addonAfter` 并集)。 */
|
|
28
|
+
addonAfter?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* 尺寸。
|
|
31
|
+
* @default "default"
|
|
32
|
+
*/
|
|
33
|
+
size?: 'sm' | 'default' | 'lg';
|
|
34
|
+
/** 受控值,用于 clearable / showCount 的逻辑;未传时回退到 uncontrolled 行为。 */
|
|
35
|
+
value?: string;
|
|
36
|
+
/** uncontrolled 初始值。 */
|
|
37
|
+
defaultValue?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
41
|
+
(
|
|
42
|
+
{
|
|
43
|
+
className,
|
|
44
|
+
type = 'text',
|
|
45
|
+
prefix,
|
|
46
|
+
suffix,
|
|
47
|
+
clearable = false,
|
|
48
|
+
showCount = false,
|
|
49
|
+
addonBefore,
|
|
50
|
+
addonAfter,
|
|
51
|
+
size = 'default',
|
|
52
|
+
value,
|
|
53
|
+
defaultValue,
|
|
54
|
+
onChange,
|
|
55
|
+
maxLength,
|
|
56
|
+
disabled,
|
|
57
|
+
...props
|
|
58
|
+
},
|
|
59
|
+
ref,
|
|
60
|
+
) => {
|
|
61
|
+
// Track the current value for clearable / showCount; mirror uncontrolled
|
|
62
|
+
// input via internal state when `value` prop isn't provided.
|
|
63
|
+
const isControlled = value !== undefined;
|
|
64
|
+
const [internal, setInternal] = React.useState<string>(
|
|
65
|
+
defaultValue ?? '',
|
|
66
|
+
);
|
|
67
|
+
const current = isControlled ? value : internal;
|
|
68
|
+
|
|
69
|
+
const innerRef = React.useRef<HTMLInputElement | null>(null);
|
|
70
|
+
React.useImperativeHandle(
|
|
71
|
+
ref,
|
|
72
|
+
() => innerRef.current as HTMLInputElement,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const h =
|
|
76
|
+
size === 'sm'
|
|
77
|
+
? 'h-8 text-xs'
|
|
78
|
+
: size === 'lg'
|
|
79
|
+
? 'h-10 text-base'
|
|
80
|
+
: 'h-9 text-sm';
|
|
81
|
+
|
|
82
|
+
const hasAffix = prefix != null || suffix != null || clearable;
|
|
83
|
+
|
|
84
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
85
|
+
if (!isControlled) setInternal(e.target.value);
|
|
86
|
+
onChange?.(e);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleClear = () => {
|
|
90
|
+
if (!isControlled) setInternal('');
|
|
91
|
+
// Synthetic event for callers wiring onChange to a setter
|
|
92
|
+
const target = innerRef.current;
|
|
93
|
+
if (target) {
|
|
94
|
+
const proto = Object.getPrototypeOf(target);
|
|
95
|
+
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
96
|
+
desc?.set?.call(target, '');
|
|
97
|
+
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Inner input + (optional) prefix/suffix wrapper
|
|
102
|
+
const innerInput = hasAffix ? (
|
|
103
|
+
<div
|
|
104
|
+
className={cn(
|
|
105
|
+
'flex items-center gap-2 rounded-md border border-input bg-background px-3 shadow-sm transition-colors focus-within:ring-1 focus-within:ring-ring',
|
|
106
|
+
h,
|
|
107
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
108
|
+
(addonBefore || addonAfter) && 'rounded-none border-x-0',
|
|
109
|
+
addonBefore && !addonAfter && 'rounded-l-none border-l-0',
|
|
110
|
+
addonAfter && !addonBefore && 'rounded-r-none border-r-0',
|
|
111
|
+
!addonBefore && !addonAfter && '',
|
|
112
|
+
className,
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{prefix ? (
|
|
116
|
+
<span className="flex shrink-0 items-center text-muted-foreground [&_svg]:size-4">
|
|
117
|
+
{prefix}
|
|
118
|
+
</span>
|
|
119
|
+
) : null}
|
|
120
|
+
<input
|
|
121
|
+
ref={innerRef}
|
|
122
|
+
type={type}
|
|
123
|
+
value={isControlled ? value : undefined}
|
|
124
|
+
defaultValue={!isControlled ? defaultValue : undefined}
|
|
125
|
+
maxLength={maxLength}
|
|
126
|
+
disabled={disabled}
|
|
127
|
+
onChange={handleChange}
|
|
128
|
+
className="grow bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
{clearable && current && current.length > 0 && !disabled ? (
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={handleClear}
|
|
135
|
+
aria-label="Clear"
|
|
136
|
+
className="shrink-0 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
137
|
+
>
|
|
138
|
+
<X className="size-4" />
|
|
139
|
+
</button>
|
|
140
|
+
) : null}
|
|
141
|
+
{suffix ? (
|
|
142
|
+
<span className="flex shrink-0 items-center text-muted-foreground [&_svg]:size-4">
|
|
143
|
+
{suffix}
|
|
144
|
+
</span>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
147
|
+
) : (
|
|
148
|
+
<input
|
|
149
|
+
ref={innerRef}
|
|
150
|
+
type={type}
|
|
151
|
+
value={isControlled ? value : undefined}
|
|
152
|
+
defaultValue={!isControlled ? defaultValue : undefined}
|
|
153
|
+
maxLength={maxLength}
|
|
154
|
+
disabled={disabled}
|
|
155
|
+
onChange={handleChange}
|
|
156
|
+
className={cn(
|
|
157
|
+
'flex w-full rounded-md border border-input bg-background px-3 py-1 shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
|
158
|
+
h,
|
|
159
|
+
(addonBefore || addonAfter) && 'rounded-none',
|
|
160
|
+
addonBefore && !addonAfter && 'rounded-l-none',
|
|
161
|
+
addonAfter && !addonBefore && 'rounded-r-none',
|
|
162
|
+
className,
|
|
163
|
+
)}
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Wrap with addons if any
|
|
169
|
+
const addonWrapper =
|
|
170
|
+
addonBefore || addonAfter ? (
|
|
171
|
+
<div className="flex">
|
|
172
|
+
{addonBefore ? (
|
|
173
|
+
<span
|
|
174
|
+
className={cn(
|
|
175
|
+
'inline-flex shrink-0 items-center rounded-l-md border border-r-0 border-input bg-muted px-3 text-muted-foreground',
|
|
176
|
+
h,
|
|
177
|
+
)}
|
|
178
|
+
>
|
|
179
|
+
{addonBefore}
|
|
180
|
+
</span>
|
|
181
|
+
) : null}
|
|
182
|
+
{innerInput}
|
|
183
|
+
{addonAfter ? (
|
|
184
|
+
<span
|
|
185
|
+
className={cn(
|
|
186
|
+
'inline-flex shrink-0 items-center rounded-r-md border border-l-0 border-input bg-muted px-3 text-muted-foreground',
|
|
187
|
+
h,
|
|
188
|
+
)}
|
|
189
|
+
>
|
|
190
|
+
{addonAfter}
|
|
191
|
+
</span>
|
|
192
|
+
) : null}
|
|
193
|
+
</div>
|
|
194
|
+
) : (
|
|
195
|
+
innerInput
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (showCount && maxLength) {
|
|
199
|
+
return (
|
|
200
|
+
<div className="space-y-1">
|
|
201
|
+
{addonWrapper}
|
|
202
|
+
<div className="text-right text-xs text-muted-foreground tabular-nums">
|
|
203
|
+
{current?.length ?? 0} / {maxLength}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return addonWrapper;
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
Input.displayName = 'Input';
|
|
212
|
+
|
|
213
|
+
export { Input };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: input-group
|
|
3
|
+
name: InputGroup
|
|
4
|
+
type: component
|
|
5
|
+
category: form
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# InputGroup
|
|
11
|
+
|
|
12
|
+
输入框 + addon 统一容器 — shadcn 2025-10 新增。**等价 antd `Input` 的 `prefix` / `suffix` / `addonBefore` / `addonAfter` 集合**,但拆为更通用的复合插槽:`InputGroupAddon`(图标 / 文本 / 按钮)+ `InputGroupInput` / `InputGroupTextarea`(主体)。
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- 输入框需要前 / 后缀(`https://` `.com`、`$`、`/月`)
|
|
17
|
+
- 输入框 + 操作按钮(搜索、清空、复制)
|
|
18
|
+
- 输入框 + 图标(搜索图标、loading spinner)
|
|
19
|
+
- Textarea + 上传 / 录音类辅助按钮
|
|
20
|
+
|
|
21
|
+
## When NOT to use
|
|
22
|
+
|
|
23
|
+
- 仅需输入框 → `Input`
|
|
24
|
+
- 仅需 textarea → `Textarea`
|
|
25
|
+
- 多个按钮拼接 → `ButtonGroup`
|
|
26
|
+
|
|
27
|
+
<!-- auto:props:begin -->
|
|
28
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
29
|
+
| --- | --- | --- | --- | --- |
|
|
30
|
+
| `disabled` | `boolean` | – | – | 整组禁用样式(子项请单独传 `disabled`,这里仅影响视觉上的 opacity)。 |
|
|
31
|
+
<!-- auto:props:end -->
|
|
32
|
+
|
|
33
|
+
<!-- auto:deps:begin -->
|
|
34
|
+
### 同库依赖
|
|
35
|
+
|
|
36
|
+
> `teamix-evo ui add input-group` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
37
|
+
|
|
38
|
+
| Entry | 类型 | 描述 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
41
|
+
|
|
42
|
+
### npm 依赖
|
|
43
|
+
|
|
44
|
+
_无 — 本组件不依赖任何 npm 包。_
|
|
45
|
+
<!-- auto:deps:end -->
|
|
46
|
+
|
|
47
|
+
## AI 生成纪律
|
|
48
|
+
|
|
49
|
+
- **顺序按 JSX 写**:`<InputGroup>` 内的子节点顺序就是渲染顺序 — `<Addon position="before" />` 必须在 `<Input />` 前面,反之亦然
|
|
50
|
+
- **不要在 InputGroupAddon 里再放 Input** — addon 是辅助槽,主输入必须用 `InputGroupInput` / `InputGroupTextarea`
|
|
51
|
+
- **`variant="icon"`** 给图标用(无背景、紧凑);**`variant="text"`** 给纯文本前后缀(muted 背景);**`variant="button"`** 给嵌入 Button(去内边距 + 去圆角,避免双重边线)
|
|
52
|
+
- **disabled**:整组传 `disabled` 仅影响外观;子项的真实 disabled 需要单独传(防止误整组禁用)
|
|
53
|
+
- **嵌入 `Button`**:用 `<InputGroupAddon variant="button">` 包,Button 自身 `variant="ghost"` 效果最自然
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import {
|
|
59
|
+
InputGroup,
|
|
60
|
+
InputGroupAddon,
|
|
61
|
+
InputGroupInput,
|
|
62
|
+
InputGroupTextarea,
|
|
63
|
+
} from '@/components/ui/input-group';
|
|
64
|
+
import { Button } from '@/components/ui/button';
|
|
65
|
+
import { Search, Send } from 'lucide-react';
|
|
66
|
+
|
|
67
|
+
// 前缀图标 + 后缀按钮
|
|
68
|
+
<InputGroup>
|
|
69
|
+
<InputGroupAddon position="before" variant="icon">
|
|
70
|
+
<Search className="size-4" />
|
|
71
|
+
</InputGroupAddon>
|
|
72
|
+
<InputGroupInput placeholder="搜索文档..." />
|
|
73
|
+
<InputGroupAddon position="after" variant="button">
|
|
74
|
+
<Button variant="ghost" size="sm">搜索</Button>
|
|
75
|
+
</InputGroupAddon>
|
|
76
|
+
</InputGroup>
|
|
77
|
+
|
|
78
|
+
// 前后缀文本(URL 输入)
|
|
79
|
+
<InputGroup>
|
|
80
|
+
<InputGroupAddon position="before">https://</InputGroupAddon>
|
|
81
|
+
<InputGroupInput defaultValue="example" />
|
|
82
|
+
<InputGroupAddon position="after">.com</InputGroupAddon>
|
|
83
|
+
</InputGroup>
|
|
84
|
+
|
|
85
|
+
// Textarea + 发送按钮
|
|
86
|
+
<InputGroup>
|
|
87
|
+
<InputGroupTextarea rows={3} placeholder="发表评论..." />
|
|
88
|
+
<InputGroupAddon position="after" variant="button">
|
|
89
|
+
<Button size="icon" icon={<Send />} aria-label="发送" />
|
|
90
|
+
</InputGroupAddon>
|
|
91
|
+
</InputGroup>
|
|
92
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Search, Send } from 'lucide-react';
|
|
3
|
+
import {
|
|
4
|
+
InputGroup,
|
|
5
|
+
InputGroupAddon,
|
|
6
|
+
InputGroupInput,
|
|
7
|
+
InputGroupTextarea,
|
|
8
|
+
} from './input-group';
|
|
9
|
+
import { Button } from '@/components/button/button';
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof InputGroup> = {
|
|
12
|
+
title: '表单与输入 · Form/InputGroup',
|
|
13
|
+
component: InputGroup,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
parameters: {
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
'输入框 + addon 统一容器 — 拆为通用复合插槽:InputGroupAddon(图标 / 文本 / 按钮)+ InputGroupInput / InputGroupTextarea(主体)。shadcn 2025-10 新增,等价 antd Input 的 prefix / suffix / addonBefore / addonAfter 集合。视觉走 OpenTrek tokens,所有样式来自 `@teamix-evo/design`,无 mock。',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof InputGroup>;
|
|
27
|
+
|
|
28
|
+
export const Playground: Story = {
|
|
29
|
+
parameters: { controls: { disable: true } },
|
|
30
|
+
render: () => (
|
|
31
|
+
<InputGroup className="w-80">
|
|
32
|
+
<InputGroupAddon position="before" variant="icon">
|
|
33
|
+
<Search className="size-4" />
|
|
34
|
+
</InputGroupAddon>
|
|
35
|
+
<InputGroupInput placeholder="搜索文档..." />
|
|
36
|
+
</InputGroup>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Url: Story = {
|
|
41
|
+
parameters: { controls: { disable: true } },
|
|
42
|
+
render: () => (
|
|
43
|
+
<InputGroup className="w-80">
|
|
44
|
+
<InputGroupAddon position="before">https://</InputGroupAddon>
|
|
45
|
+
<InputGroupInput defaultValue="example" />
|
|
46
|
+
<InputGroupAddon position="after">.com</InputGroupAddon>
|
|
47
|
+
</InputGroup>
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const SearchWithButton: Story = {
|
|
52
|
+
parameters: { controls: { disable: true } },
|
|
53
|
+
render: () => (
|
|
54
|
+
<InputGroup className="w-96">
|
|
55
|
+
<InputGroupAddon position="before" variant="icon">
|
|
56
|
+
<Search className="size-4" />
|
|
57
|
+
</InputGroupAddon>
|
|
58
|
+
<InputGroupInput placeholder="输入关键词..." />
|
|
59
|
+
<InputGroupAddon position="after" variant="button">
|
|
60
|
+
<Button variant="ghost" size="sm">
|
|
61
|
+
搜索
|
|
62
|
+
</Button>
|
|
63
|
+
</InputGroupAddon>
|
|
64
|
+
</InputGroup>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const TextareaWithSend: Story = {
|
|
69
|
+
parameters: { controls: { disable: true } },
|
|
70
|
+
render: () => (
|
|
71
|
+
<InputGroup className="w-96 items-end">
|
|
72
|
+
<InputGroupTextarea rows={3} placeholder="发表评论..." />
|
|
73
|
+
<InputGroupAddon position="after" variant="button">
|
|
74
|
+
<Button size="icon" icon={<Send />} aria-label="发送" />
|
|
75
|
+
</InputGroupAddon>
|
|
76
|
+
</InputGroup>
|
|
77
|
+
),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Disabled: Story = {
|
|
81
|
+
parameters: { controls: { disable: true } },
|
|
82
|
+
render: () => (
|
|
83
|
+
<InputGroup className="w-80" disabled>
|
|
84
|
+
<InputGroupAddon position="before">@</InputGroupAddon>
|
|
85
|
+
<InputGroupInput defaultValue="alice" disabled />
|
|
86
|
+
</InputGroup>
|
|
87
|
+
),
|
|
88
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
|
|
5
|
+
export interface InputGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/**
|
|
7
|
+
* 整组禁用样式(子项请单独传 `disabled`,这里仅影响视觉上的 opacity)。
|
|
8
|
+
*/
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Input + addon 统一容器 — shadcn 2025-10 新增,等价 antd `Input` 的
|
|
14
|
+
* `prefix` / `suffix` / `addonBefore` / `addonAfter` 集合,但拆为更通用的
|
|
15
|
+
* **复合插槽**:`<InputGroupAddon position="before|after">`(addon)+
|
|
16
|
+
* `<InputGroupInput>` / `<InputGroupTextarea>`(主体),按需嵌套图标 / 按钮 / 文本。
|
|
17
|
+
*/
|
|
18
|
+
const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
|
19
|
+
({ className, disabled, children, ...props }, ref) => (
|
|
20
|
+
<div
|
|
21
|
+
ref={ref}
|
|
22
|
+
data-disabled={disabled ? '' : undefined}
|
|
23
|
+
className={cn(
|
|
24
|
+
'flex w-full items-stretch overflow-hidden rounded-md border border-input bg-background shadow-sm',
|
|
25
|
+
'focus-within:ring-1 focus-within:ring-ring',
|
|
26
|
+
'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
InputGroup.displayName = 'InputGroup';
|
|
36
|
+
|
|
37
|
+
export interface InputGroupAddonProps
|
|
38
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
39
|
+
/**
|
|
40
|
+
* 位于输入框的哪一侧。
|
|
41
|
+
* @default "before"
|
|
42
|
+
*/
|
|
43
|
+
position?: 'before' | 'after';
|
|
44
|
+
/**
|
|
45
|
+
* `text` = 默认 muted 文本背景 / `icon` = 紧凑、透明背景(图标专用)/
|
|
46
|
+
* `button` = 用于嵌入 `<Button>`(去内边距)。
|
|
47
|
+
* @default "text"
|
|
48
|
+
*/
|
|
49
|
+
variant?: 'text' | 'icon' | 'button';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const InputGroupAddon = React.forwardRef<HTMLDivElement, InputGroupAddonProps>(
|
|
53
|
+
({ position = 'before', variant = 'text', className, ...props }, ref) => (
|
|
54
|
+
<div
|
|
55
|
+
ref={ref}
|
|
56
|
+
data-position={position}
|
|
57
|
+
className={cn(
|
|
58
|
+
'flex shrink-0 items-center',
|
|
59
|
+
variant === 'text' &&
|
|
60
|
+
'border-input bg-muted px-3 text-sm text-muted-foreground',
|
|
61
|
+
variant === 'icon' && 'px-2 text-muted-foreground',
|
|
62
|
+
variant === 'button' && '[&>*]:rounded-none [&>*]:border-0 [&>*]:shadow-none',
|
|
63
|
+
position === 'before' && variant !== 'icon' && 'border-r',
|
|
64
|
+
position === 'after' && variant !== 'icon' && 'border-l',
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
InputGroupAddon.displayName = 'InputGroupAddon';
|
|
72
|
+
|
|
73
|
+
const InputGroupInput = React.forwardRef<
|
|
74
|
+
HTMLInputElement,
|
|
75
|
+
React.InputHTMLAttributes<HTMLInputElement>
|
|
76
|
+
>(({ className, type = 'text', ...props }, ref) => (
|
|
77
|
+
<input
|
|
78
|
+
ref={ref}
|
|
79
|
+
type={type}
|
|
80
|
+
className={cn(
|
|
81
|
+
'flex h-9 w-full min-w-0 flex-1 bg-transparent px-3 py-1 text-sm placeholder:text-muted-foreground',
|
|
82
|
+
'focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
));
|
|
88
|
+
InputGroupInput.displayName = 'InputGroupInput';
|
|
89
|
+
|
|
90
|
+
const InputGroupTextarea = React.forwardRef<
|
|
91
|
+
HTMLTextAreaElement,
|
|
92
|
+
React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
93
|
+
>(({ className, rows = 3, ...props }, ref) => (
|
|
94
|
+
<textarea
|
|
95
|
+
ref={ref}
|
|
96
|
+
rows={rows}
|
|
97
|
+
className={cn(
|
|
98
|
+
'flex w-full min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground',
|
|
99
|
+
'focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
100
|
+
className,
|
|
101
|
+
)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
));
|
|
105
|
+
InputGroupTextarea.displayName = 'InputGroupTextarea';
|
|
106
|
+
|
|
107
|
+
export { InputGroup, InputGroupAddon, InputGroupInput, InputGroupTextarea };
|