@teamix-evo/ui 0.5.0 → 0.6.0
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/package.json +19 -17
- package/src/components/alert/index.tsx +1 -1
- package/src/components/alert-dialog/index.tsx +17 -24
- package/src/components/alert-dialog/meta.md +102 -8
- package/src/components/alert-dialog/stories.tsx +117 -7
- package/src/components/avatar/index.tsx +1 -1
- package/src/components/badge/index.tsx +1 -1
- package/src/components/button/index.tsx +3 -31
- package/src/components/button/meta.md +24 -13
- package/src/components/button/stories.tsx +24 -12
- package/src/components/button-group/meta.md +6 -9
- package/src/components/button-group/stories.tsx +2 -6
- package/src/components/calendar/index.tsx +12 -7
- package/src/components/cascader-select/index.tsx +1 -1
- package/src/components/checkbox/index.tsx +1 -1
- package/src/components/combobox/index.tsx +54 -10
- package/src/components/combobox/meta.md +3 -5
- package/src/components/combobox/stories.tsx +104 -25
- package/src/components/data-table/index.tsx +10 -5
- package/src/components/data-table/stories.tsx +4 -1
- package/src/components/date-picker/index.tsx +25 -2
- package/src/components/field/index.tsx +1 -1
- package/src/components/filter-bar/index.tsx +1 -1
- package/src/components/float-button/meta.md +3 -15
- package/src/components/icon/index.tsx +3 -4
- package/src/components/icon/meta.md +1 -2
- package/src/components/input/index.tsx +11 -3
- package/src/components/input-group/index.tsx +33 -23
- package/src/components/input-group/meta.md +15 -0
- package/src/components/input-group/stories.tsx +14 -0
- package/src/components/input-ip/index.tsx +1 -1
- package/src/components/input-number/index.tsx +5 -5
- package/src/components/item/meta.md +10 -42
- package/src/components/item/stories.tsx +12 -44
- package/src/components/radio-group/index.tsx +1 -1
- package/src/components/rate/index.tsx +3 -3
- package/src/components/select/index.tsx +2 -2
- package/src/components/skeleton/index.tsx +1 -1
- package/src/components/skeleton/meta.md +6 -6
- package/src/components/skeleton/stories.tsx +8 -8
- package/src/components/slider/index.tsx +27 -1
- package/src/components/sonner/index.tsx +43 -40
- package/src/components/sonner/meta.md +84 -68
- package/src/components/sonner/stories.tsx +122 -83
- package/src/components/spinner/index.tsx +170 -0
- package/src/components/spinner/meta.md +27 -1
- package/src/components/spinner/stories.tsx +23 -0
- package/src/components/steps/index.tsx +5 -1
- package/src/components/switch/index.tsx +1 -1
- package/src/components/table/index.tsx +6 -2
- package/src/components/tag/index.tsx +14 -0
- package/src/components/tag/meta.md +1 -0
- package/src/components/tag/stories.tsx +13 -0
- package/src/components/textarea/index.tsx +1 -1
- package/src/components/textarea/stories.tsx +1 -1
- package/src/components/time-picker/index.tsx +3 -1
- package/src/components/toggle/index.tsx +1 -1
- package/src/components/tooltip/index.tsx +5 -1
- package/src/components/tooltip/meta.md +13 -28
- package/src/components/tooltip/stories.tsx +11 -28
- package/src/components/transfer/index.tsx +12 -10
- package/src/components/tree-select/index.tsx +1 -1
- package/LICENSE +0 -21
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from 'date-fns';
|
|
30
30
|
import { zhCN } from 'date-fns/locale';
|
|
31
31
|
import {
|
|
32
|
-
|
|
32
|
+
CalendarDays as CalendarIcon,
|
|
33
33
|
ChevronLeft,
|
|
34
34
|
ChevronRight,
|
|
35
35
|
ChevronsLeft,
|
|
@@ -578,6 +578,9 @@ function DatePicker({
|
|
|
578
578
|
dateToParts(value),
|
|
579
579
|
);
|
|
580
580
|
|
|
581
|
+
// Track pointer-down inside popover to suppress blur side-effects
|
|
582
|
+
const pointerInPanelRef = React.useRef(false);
|
|
583
|
+
|
|
581
584
|
// input text
|
|
582
585
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
583
586
|
const anchorRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -611,6 +614,7 @@ function DatePicker({
|
|
|
611
614
|
};
|
|
612
615
|
|
|
613
616
|
const handleInputBlur = () => {
|
|
617
|
+
if (pointerInPanelRef.current) return;
|
|
614
618
|
const parsed = tryParseDate(inputText, displayFormat);
|
|
615
619
|
if (parsed) commitValue(parsed);
|
|
616
620
|
else if (inputText.trim() === '') commitValue(undefined);
|
|
@@ -724,6 +728,7 @@ function DatePicker({
|
|
|
724
728
|
<CalendarIcon
|
|
725
729
|
data-slot="date-picker-icon"
|
|
726
730
|
aria-hidden
|
|
731
|
+
strokeWidth={1.5}
|
|
727
732
|
className="size-4 shrink-0 text-muted-foreground"
|
|
728
733
|
/>
|
|
729
734
|
)}
|
|
@@ -732,6 +737,12 @@ function DatePicker({
|
|
|
732
737
|
<PopoverContent
|
|
733
738
|
data-slot="date-picker-content"
|
|
734
739
|
className="w-auto rounded-md p-0"
|
|
740
|
+
onPointerDown={() => {
|
|
741
|
+
pointerInPanelRef.current = true;
|
|
742
|
+
}}
|
|
743
|
+
onPointerUp={() => {
|
|
744
|
+
pointerInPanelRef.current = false;
|
|
745
|
+
}}
|
|
735
746
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
736
747
|
onInteractOutside={(e) => {
|
|
737
748
|
const target = e.detail.originalEvent.target as Node | null;
|
|
@@ -806,7 +817,7 @@ function DatePicker({
|
|
|
806
817
|
) : (
|
|
807
818
|
<Calendar
|
|
808
819
|
mode="single"
|
|
809
|
-
selected={
|
|
820
|
+
selected={draftDate}
|
|
810
821
|
onSelect={handleSelectDate}
|
|
811
822
|
disabled={disabledDates}
|
|
812
823
|
defaultMonth={value}
|
|
@@ -972,6 +983,9 @@ function DateRangePicker({
|
|
|
972
983
|
const [hoveredDate, setHoveredDate] = React.useState<Date | undefined>();
|
|
973
984
|
const [activeField, setActiveField] = React.useState<'from' | 'to'>('from');
|
|
974
985
|
|
|
986
|
+
// Track pointer-down inside popover to suppress blur side-effects
|
|
987
|
+
const pointerInPanelRef2 = React.useRef(false);
|
|
988
|
+
|
|
975
989
|
const fromInputRef = React.useRef<HTMLInputElement>(null);
|
|
976
990
|
const toInputRef = React.useRef<HTMLInputElement>(null);
|
|
977
991
|
const anchorRef = React.useRef<HTMLDivElement>(null);
|
|
@@ -1115,6 +1129,7 @@ function DateRangePicker({
|
|
|
1115
1129
|
};
|
|
1116
1130
|
|
|
1117
1131
|
const handleFromBlur = () => {
|
|
1132
|
+
if (pointerInPanelRef2.current) return;
|
|
1118
1133
|
const t = fromText.trim();
|
|
1119
1134
|
if (!t) {
|
|
1120
1135
|
// 仅当之前已有起始值时,才视为用户主动清空。
|
|
@@ -1145,6 +1160,7 @@ function DateRangePicker({
|
|
|
1145
1160
|
};
|
|
1146
1161
|
|
|
1147
1162
|
const handleToBlur = () => {
|
|
1163
|
+
if (pointerInPanelRef2.current) return;
|
|
1148
1164
|
const t = toText.trim();
|
|
1149
1165
|
if (!t) {
|
|
1150
1166
|
// 同 handleFromBlur:仅当之前已有结束值时才主动清空,避免吞掉日历首次点击
|
|
@@ -1375,6 +1391,7 @@ function DateRangePicker({
|
|
|
1375
1391
|
<CalendarIcon
|
|
1376
1392
|
data-slot="date-range-picker-icon"
|
|
1377
1393
|
aria-hidden
|
|
1394
|
+
strokeWidth={1.5}
|
|
1378
1395
|
className="size-4 shrink-0 text-muted-foreground"
|
|
1379
1396
|
/>
|
|
1380
1397
|
)}
|
|
@@ -1383,6 +1400,12 @@ function DateRangePicker({
|
|
|
1383
1400
|
<PopoverContent
|
|
1384
1401
|
data-slot="date-range-picker-content"
|
|
1385
1402
|
className={cn('w-auto rounded-md p-0', RANGE_PREVIEW_BASE_CLASS)}
|
|
1403
|
+
onPointerDown={() => {
|
|
1404
|
+
pointerInPanelRef2.current = true;
|
|
1405
|
+
}}
|
|
1406
|
+
onPointerUp={() => {
|
|
1407
|
+
pointerInPanelRef2.current = false;
|
|
1408
|
+
}}
|
|
1386
1409
|
onPointerLeave={() => setHoveredDate(undefined)}
|
|
1387
1410
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
1388
1411
|
onInteractOutside={(e) => {
|
|
@@ -209,7 +209,7 @@ function FieldInsetBox({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
209
209
|
data-slot="field-inset-box"
|
|
210
210
|
className={cn(
|
|
211
211
|
'flex h-8 w-full items-center rounded-md border border-input bg-card',
|
|
212
|
-
'focus-within:border-ring focus-within:ring-
|
|
212
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
213
213
|
'group-data-[invalid=true]/field:!border-destructive group-data-[invalid=true]/field:!shadow-none',
|
|
214
214
|
'group-data-[invalid=true]/field:focus-within:!border-destructive group-data-[invalid=true]/field:focus-within:ring-destructive/20',
|
|
215
215
|
INSET_INNER_RESET,
|
|
@@ -418,7 +418,7 @@ function FilterBarSearch({
|
|
|
418
418
|
data-slot="filter-bar-search"
|
|
419
419
|
className={cn(
|
|
420
420
|
'flex h-8 items-center overflow-hidden rounded-md border border-input bg-card',
|
|
421
|
-
'focus-within:border-ring focus-within:ring-
|
|
421
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
422
422
|
className,
|
|
423
423
|
)}
|
|
424
424
|
{...props}
|
|
@@ -35,16 +35,8 @@
|
|
|
35
35
|
|
|
36
36
|
```tsx
|
|
37
37
|
<DemoContainer>
|
|
38
|
-
<FloatButton
|
|
39
|
-
|
|
40
|
-
aria-label="客服"
|
|
41
|
-
badge="3"
|
|
42
|
-
/>
|
|
43
|
-
<FloatButton
|
|
44
|
-
variant="default"
|
|
45
|
-
icon={<PlusIcon />}
|
|
46
|
-
aria-label="新建"
|
|
47
|
-
/>
|
|
38
|
+
<FloatButton icon={<MessageCircleIcon />} aria-label="客服" badge="3" />
|
|
39
|
+
<FloatButton variant="default" icon={<PlusIcon />} aria-label="新建" />
|
|
48
40
|
</DemoContainer>
|
|
49
41
|
```
|
|
50
42
|
|
|
@@ -54,11 +46,7 @@
|
|
|
54
46
|
|
|
55
47
|
```tsx
|
|
56
48
|
<DemoContainer>
|
|
57
|
-
<FloatButton
|
|
58
|
-
shape="square"
|
|
59
|
-
icon={<PlusIcon />}
|
|
60
|
-
aria-label="新建"
|
|
61
|
-
/>
|
|
49
|
+
<FloatButton shape="square" icon={<PlusIcon />} aria-label="新建" />
|
|
62
50
|
</DemoContainer>
|
|
63
51
|
```
|
|
64
52
|
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* <span className="text-success"><SuccessFilledIcon /></span>
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
|
-
* 尺寸:默认 `size-
|
|
24
|
-
* viewBox 采用 `-1 -1 22 22`,使图形在容器内自带 1px 内边距留白。
|
|
23
|
+
* 尺寸:默认 `size-4`(16px),通过 `className` 覆盖(如 `size-3.5` / `size-5`)。
|
|
25
24
|
*/
|
|
26
25
|
import * as React from 'react';
|
|
27
26
|
|
|
@@ -37,9 +36,9 @@ function BaseFilledIcon({
|
|
|
37
36
|
return (
|
|
38
37
|
<svg
|
|
39
38
|
data-slot="icon"
|
|
40
|
-
viewBox="
|
|
39
|
+
viewBox="0 0 20 20"
|
|
41
40
|
aria-hidden="true"
|
|
42
|
-
className={cn('size-
|
|
41
|
+
className={cn('size-4 shrink-0', className)}
|
|
43
42
|
{...props}
|
|
44
43
|
>
|
|
45
44
|
{children}
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
- ```tsx
|
|
23
23
|
- <span className="text-success"><SuccessFilledIcon /></span>
|
|
24
24
|
- ```
|
|
25
|
-
- 尺寸:默认 `size-
|
|
26
|
-
- viewBox 采用 `-1 -1 22 22`,使图形在容器内自带 1px 内边距留白。
|
|
25
|
+
- 尺寸:默认 `size-4`(16px),通过 `className` 覆盖(如 `size-3.5` / `size-5`)。
|
|
27
26
|
|
|
28
27
|
## 示例
|
|
29
28
|
|
|
@@ -17,7 +17,7 @@ import { cn } from '@/lib/utils';
|
|
|
17
17
|
// ─── Input cva ──────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
const inputVariants = cva(
|
|
20
|
-
'w-full min-w-0 cursor-text rounded-md border border-input bg-transparent font-normal transition-colors outline-none file:inline-flex file:cursor-pointer file:border-0 file:bg-transparent file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-
|
|
20
|
+
'w-full min-w-0 cursor-text rounded-md border border-input bg-transparent font-normal transition-colors outline-none file:inline-flex file:cursor-pointer file:border-0 file:bg-transparent file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/20 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20',
|
|
21
21
|
{
|
|
22
22
|
variants: {
|
|
23
23
|
size: {
|
|
@@ -46,11 +46,19 @@ export interface InputProps
|
|
|
46
46
|
// ─── Input ──────────────────────────────────────────────────────────────────
|
|
47
47
|
|
|
48
48
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
49
|
-
({ className, type, size, ...props }, ref) => {
|
|
49
|
+
({ className, type, size, role: roleProp, ...props }, ref) => {
|
|
50
|
+
// <input type="search"> 会被浏览器渲染原生 clear / 放大镜 / IE Reveal 按钮,
|
|
51
|
+
// 与 Input 自带的 `clearable` / `suffix={<Search/>}` 视觉冲突。组件层透明屏蔽:
|
|
52
|
+
// 渲染为 type="text" + role="searchbox",保留无障碍语义,免除业务侧 globals.css
|
|
53
|
+
// 补 webkit/ms 伪元素 reset(原 ADR 0023 §4 “手动加 CSS” 路径)。
|
|
54
|
+
const isSearch = type === 'search';
|
|
55
|
+
const safeType = isSearch ? 'text' : type;
|
|
56
|
+
const role = isSearch ? roleProp ?? 'searchbox' : roleProp;
|
|
50
57
|
return (
|
|
51
58
|
<input
|
|
52
59
|
ref={ref}
|
|
53
|
-
type={
|
|
60
|
+
type={safeType}
|
|
61
|
+
role={role}
|
|
54
62
|
data-slot="input"
|
|
55
63
|
className={cn(inputVariants({ size, className }))}
|
|
56
64
|
{...props}
|
|
@@ -32,7 +32,7 @@ const InputGroup = React.forwardRef<
|
|
|
32
32
|
data-slot="input-group"
|
|
33
33
|
role="group"
|
|
34
34
|
className={cn(
|
|
35
|
-
'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-md border border-input transition-colors outline-none has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-
|
|
35
|
+
'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-md border border-input transition-colors outline-none has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/20 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-1 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
|
|
36
36
|
className,
|
|
37
37
|
)}
|
|
38
38
|
{...props}
|
|
@@ -136,7 +136,7 @@ function InputGroupInput({ className, ...props }: InputProps) {
|
|
|
136
136
|
<Input
|
|
137
137
|
data-slot="input-group-control"
|
|
138
138
|
className={cn(
|
|
139
|
-
'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0',
|
|
139
|
+
'h-full flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0',
|
|
140
140
|
className,
|
|
141
141
|
)}
|
|
142
142
|
{...props}
|
|
@@ -163,10 +163,7 @@ function InputGroupTextarea({
|
|
|
163
163
|
// ─── InputGroupClear ──────────────────────────────────────────────────────────────
|
|
164
164
|
|
|
165
165
|
export interface InputGroupClearProps
|
|
166
|
-
extends Omit<
|
|
167
|
-
React.ComponentProps<typeof InputGroupButton>,
|
|
168
|
-
'children' | 'aria-label'
|
|
169
|
-
> {
|
|
166
|
+
extends Omit<React.ComponentProps<'span'>, 'children'> {
|
|
170
167
|
/** 是否显示。受控用法下一般绑定 `value.length > 0`。 @default true */
|
|
171
168
|
visible?: boolean;
|
|
172
169
|
/** 无障碍 label。 @default '清除' */
|
|
@@ -181,32 +178,36 @@ function InputGroupClear({
|
|
|
181
178
|
visible = true,
|
|
182
179
|
className,
|
|
183
180
|
'aria-label': ariaLabel = '清除',
|
|
181
|
+
onClick,
|
|
184
182
|
...props
|
|
185
183
|
}: InputGroupClearProps) {
|
|
186
184
|
if (!visible) return null;
|
|
187
185
|
return (
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
size="icon-xs"
|
|
186
|
+
<span
|
|
187
|
+
role="button"
|
|
191
188
|
aria-label={ariaLabel}
|
|
189
|
+
tabIndex={-1}
|
|
190
|
+
data-slot="input-group-clear"
|
|
191
|
+
onPointerDown={(e) => {
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
}}
|
|
195
|
+
onClick={onClick}
|
|
192
196
|
className={cn(
|
|
193
|
-
'text-muted-foreground
|
|
197
|
+
'inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground',
|
|
194
198
|
className,
|
|
195
199
|
)}
|
|
196
200
|
{...props}
|
|
197
201
|
>
|
|
198
|
-
<XIcon />
|
|
199
|
-
</
|
|
202
|
+
<XIcon className="size-3.5" />
|
|
203
|
+
</span>
|
|
200
204
|
);
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
// ─── InputGroupPasswordToggle ───────────────────────────────────────────────────────
|
|
204
208
|
|
|
205
209
|
export interface InputGroupPasswordToggleProps
|
|
206
|
-
extends Omit<
|
|
207
|
-
React.ComponentProps<typeof InputGroupButton>,
|
|
208
|
-
'children' | 'onClick'
|
|
209
|
-
> {
|
|
210
|
+
extends Omit<React.ComponentProps<'span'>, 'children' | 'onClick'> {
|
|
210
211
|
/** 当前密码是否可见。 */
|
|
211
212
|
visible: boolean;
|
|
212
213
|
/** 切换可见性,由父级同步修改 `<InputGroupInput type>` 。 */
|
|
@@ -224,20 +225,29 @@ function InputGroupPasswordToggle({
|
|
|
224
225
|
...props
|
|
225
226
|
}: InputGroupPasswordToggleProps) {
|
|
226
227
|
return (
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
size="icon-xs"
|
|
228
|
+
<span
|
|
229
|
+
role="button"
|
|
230
230
|
aria-label={visible ? '隐藏密码' : '显示密码'}
|
|
231
231
|
aria-pressed={visible}
|
|
232
|
+
tabIndex={-1}
|
|
233
|
+
data-slot="input-group-password-toggle"
|
|
234
|
+
onPointerDown={(e) => {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
e.stopPropagation();
|
|
237
|
+
}}
|
|
232
238
|
onClick={() => onVisibleChange(!visible)}
|
|
233
239
|
className={cn(
|
|
234
|
-
'text-muted-foreground
|
|
240
|
+
'inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground',
|
|
235
241
|
className,
|
|
236
242
|
)}
|
|
237
243
|
{...props}
|
|
238
244
|
>
|
|
239
|
-
{visible ?
|
|
240
|
-
|
|
245
|
+
{visible ? (
|
|
246
|
+
<EyeOffIcon className="size-3.5" />
|
|
247
|
+
) : (
|
|
248
|
+
<EyeIcon className="size-3.5" />
|
|
249
|
+
)}
|
|
250
|
+
</span>
|
|
241
251
|
);
|
|
242
252
|
}
|
|
243
253
|
|
|
@@ -268,7 +278,7 @@ function InputGroupShowCount({
|
|
|
268
278
|
data-slot="input-group-show-count"
|
|
269
279
|
data-overflow={overflow || undefined}
|
|
270
280
|
className={cn(
|
|
271
|
-
'flex items-center text-xs tabular-nums text-muted-foreground select-none',
|
|
281
|
+
'flex items-center text-xs tabular-nums text-muted-foreground/60 select-none',
|
|
272
282
|
'data-[overflow]:text-destructive',
|
|
273
283
|
className,
|
|
274
284
|
)}
|
|
@@ -44,6 +44,21 @@
|
|
|
44
44
|
</InputGroup>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### WithTrailingIcon
|
|
48
|
+
|
|
49
|
+
后置图标插槽:搜索图标在右侧。
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<InputGroup className="w-64">
|
|
53
|
+
<InputGroupInput placeholder="搜索..." />
|
|
54
|
+
<InputGroupAddon align="inline-end">
|
|
55
|
+
<InputGroupText>
|
|
56
|
+
<SearchIcon />
|
|
57
|
+
</InputGroupText>
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
</InputGroup>
|
|
60
|
+
```
|
|
61
|
+
|
|
47
62
|
### Email
|
|
48
63
|
|
|
49
64
|
邮箱输入:左侧邮件图标 + 右侧域名后缀文本。
|
|
@@ -44,6 +44,20 @@ export const WithLeadingIcon: Story = {
|
|
|
44
44
|
),
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
/** 后置图标插槽:搜索图标在右侧。 */
|
|
48
|
+
export const WithTrailingIcon: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<InputGroup className="w-64">
|
|
51
|
+
<InputGroupInput placeholder="搜索..." />
|
|
52
|
+
<InputGroupAddon align="inline-end">
|
|
53
|
+
<InputGroupText>
|
|
54
|
+
<SearchIcon />
|
|
55
|
+
</InputGroupText>
|
|
56
|
+
</InputGroupAddon>
|
|
57
|
+
</InputGroup>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* 搜索场景:左侧 SearchIcon + 右侧 InputGroupClear 受控清除按钮。
|
|
49
63
|
* 仅当 value 非空时显示清除按钮。
|
|
@@ -27,7 +27,7 @@ const inputIpVariants = cva(
|
|
|
27
27
|
// `w-fit` 显式锁定“宽度 = 内容”,避免被 flex/grid 父容器 cross-axis stretch 拉满。
|
|
28
28
|
// 消费方需要铺满父容器时,可通过 className="w-full" 覆盖。
|
|
29
29
|
'inline-flex w-fit items-center rounded-md border border-input bg-transparent transition-colors',
|
|
30
|
-
'focus-within:border-ring focus-within:ring-
|
|
30
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
31
31
|
'aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-1 aria-[invalid=true]:ring-destructive/20',
|
|
32
32
|
].join(' '),
|
|
33
33
|
{
|
|
@@ -21,8 +21,8 @@ import { cn } from '@/lib/utils';
|
|
|
21
21
|
|
|
22
22
|
const inputNumberVariants = cva(
|
|
23
23
|
[
|
|
24
|
-
'inline-flex w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent transition-colors',
|
|
25
|
-
'focus-within:border-ring focus-within:ring-
|
|
24
|
+
'group/input-number inline-flex w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent transition-colors',
|
|
25
|
+
'focus-within:border-ring focus-within:ring-ring/20',
|
|
26
26
|
'has-[input:disabled]:cursor-not-allowed has-[input:disabled]:bg-input/50 has-[input:disabled]:opacity-50',
|
|
27
27
|
'has-[input[aria-invalid=true]]:border-destructive has-[input[aria-invalid=true]]:ring-1 has-[input[aria-invalid=true]]:ring-destructive/20',
|
|
28
28
|
].join(' '),
|
|
@@ -253,7 +253,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
253
253
|
};
|
|
254
254
|
|
|
255
255
|
const stepBtnClass =
|
|
256
|
-
'flex cursor-pointer items-center justify-center text-muted-foreground hover:bg-
|
|
256
|
+
'flex cursor-pointer items-center justify-center text-muted-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent';
|
|
257
257
|
|
|
258
258
|
const upDisabled =
|
|
259
259
|
disabled ||
|
|
@@ -325,7 +325,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
325
325
|
{controls && variant === 'default' ? (
|
|
326
326
|
<div
|
|
327
327
|
data-slot="input-number-handlers"
|
|
328
|
-
className="flex flex-col border-l border-input"
|
|
328
|
+
className="flex flex-col divide-y divide-input border-l border-input opacity-0 transition-opacity group-hover/input-number:opacity-100 group-focus-within/input-number:opacity-100"
|
|
329
329
|
>
|
|
330
330
|
<button
|
|
331
331
|
type="button"
|
|
@@ -345,7 +345,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
345
345
|
onClick={() => stepBy(-1)}
|
|
346
346
|
aria-label="减少"
|
|
347
347
|
data-slot="input-number-decrement"
|
|
348
|
-
className={cn(stepBtnClass, 'h-1/2
|
|
348
|
+
className={cn(stepBtnClass, 'h-1/2 px-1.5')}
|
|
349
349
|
>
|
|
350
350
|
<ChevronDown className="size-3.5" />
|
|
351
351
|
</button>
|
|
@@ -127,18 +127,10 @@
|
|
|
127
127
|
<span className="text-xs text-muted-foreground">3 小时前更新</span>
|
|
128
128
|
</ItemContent>
|
|
129
129
|
<ItemActions>
|
|
130
|
-
<Button
|
|
131
|
-
size="sm"
|
|
132
|
-
variant="ghost"
|
|
133
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
134
|
-
>
|
|
130
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
135
131
|
编辑
|
|
136
132
|
</Button>
|
|
137
|
-
<Button
|
|
138
|
-
size="sm"
|
|
139
|
-
variant="ghost"
|
|
140
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
141
|
-
>
|
|
133
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
142
134
|
删除
|
|
143
135
|
</Button>
|
|
144
136
|
</ItemActions>
|
|
@@ -159,18 +151,10 @@
|
|
|
159
151
|
<span className="text-xs text-muted-foreground">1 天前更新</span>
|
|
160
152
|
</ItemContent>
|
|
161
153
|
<ItemActions>
|
|
162
|
-
<Button
|
|
163
|
-
size="sm"
|
|
164
|
-
variant="ghost"
|
|
165
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
166
|
-
>
|
|
154
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
167
155
|
编辑
|
|
168
156
|
</Button>
|
|
169
|
-
<Button
|
|
170
|
-
size="sm"
|
|
171
|
-
variant="ghost"
|
|
172
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
173
|
-
>
|
|
157
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
174
158
|
删除
|
|
175
159
|
</Button>
|
|
176
160
|
</ItemActions>
|
|
@@ -221,26 +205,18 @@ Actions 自动分隔
|
|
|
221
205
|
<ItemDescription>点击操作按钮管理</ItemDescription>
|
|
222
206
|
</ItemContent>
|
|
223
207
|
<ItemActions separated>
|
|
224
|
-
<Button
|
|
225
|
-
size="sm"
|
|
226
|
-
variant="ghost"
|
|
227
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
228
|
-
>
|
|
208
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
229
209
|
<PencilIcon />
|
|
230
210
|
编辑
|
|
231
211
|
</Button>
|
|
232
|
-
<Button
|
|
233
|
-
size="sm"
|
|
234
|
-
variant="ghost"
|
|
235
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
236
|
-
>
|
|
212
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
237
213
|
<BookmarkIcon />
|
|
238
214
|
收藏
|
|
239
215
|
</Button>
|
|
240
216
|
<Button
|
|
241
217
|
size="sm"
|
|
242
|
-
variant="
|
|
243
|
-
className="text-destructive hover:
|
|
218
|
+
variant="link"
|
|
219
|
+
className="text-destructive hover:text-destructive/80"
|
|
244
220
|
>
|
|
245
221
|
<TrashIcon />
|
|
246
222
|
删除
|
|
@@ -314,11 +290,7 @@ Actions 自动分隔
|
|
|
314
290
|
</Item>
|
|
315
291
|
<ItemGroupFooter>
|
|
316
292
|
<span>共 2 条通知</span>
|
|
317
|
-
<Button
|
|
318
|
-
size="sm"
|
|
319
|
-
variant="ghost"
|
|
320
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
321
|
-
>
|
|
293
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
322
294
|
查看全部
|
|
323
295
|
</Button>
|
|
324
296
|
</ItemGroupFooter>
|
|
@@ -344,11 +316,7 @@ Actions 自动分隔
|
|
|
344
316
|
</ItemContent>
|
|
345
317
|
<ItemFooter>
|
|
346
318
|
<span>截止:2025-01-15</span>
|
|
347
|
-
<Button
|
|
348
|
-
size="sm"
|
|
349
|
-
variant="ghost"
|
|
350
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
351
|
-
>
|
|
319
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
352
320
|
查看
|
|
353
321
|
</Button>
|
|
354
322
|
</ItemFooter>
|
|
@@ -51,7 +51,7 @@ export const Default: Story = {
|
|
|
51
51
|
<Button
|
|
52
52
|
size="icon-sm"
|
|
53
53
|
variant="ghost"
|
|
54
|
-
className="text-muted-foreground
|
|
54
|
+
className="text-muted-foreground"
|
|
55
55
|
>
|
|
56
56
|
<MoreHorizontalIcon />
|
|
57
57
|
</Button>
|
|
@@ -69,7 +69,7 @@ export const Default: Story = {
|
|
|
69
69
|
<Button
|
|
70
70
|
size="icon-sm"
|
|
71
71
|
variant="ghost"
|
|
72
|
-
className="text-muted-foreground
|
|
72
|
+
className="text-muted-foreground"
|
|
73
73
|
>
|
|
74
74
|
<MoreHorizontalIcon />
|
|
75
75
|
</Button>
|
|
@@ -169,18 +169,10 @@ export const Horizontal: Story = {
|
|
|
169
169
|
<span className="text-xs text-muted-foreground">3 小时前更新</span>
|
|
170
170
|
</ItemContent>
|
|
171
171
|
<ItemActions>
|
|
172
|
-
<Button
|
|
173
|
-
size="sm"
|
|
174
|
-
variant="ghost"
|
|
175
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
176
|
-
>
|
|
172
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
177
173
|
编辑
|
|
178
174
|
</Button>
|
|
179
|
-
<Button
|
|
180
|
-
size="sm"
|
|
181
|
-
variant="ghost"
|
|
182
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
183
|
-
>
|
|
175
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
184
176
|
删除
|
|
185
177
|
</Button>
|
|
186
178
|
</ItemActions>
|
|
@@ -201,18 +193,10 @@ export const Horizontal: Story = {
|
|
|
201
193
|
<span className="text-xs text-muted-foreground">1 天前更新</span>
|
|
202
194
|
</ItemContent>
|
|
203
195
|
<ItemActions>
|
|
204
|
-
<Button
|
|
205
|
-
size="sm"
|
|
206
|
-
variant="ghost"
|
|
207
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
208
|
-
>
|
|
196
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
209
197
|
编辑
|
|
210
198
|
</Button>
|
|
211
|
-
<Button
|
|
212
|
-
size="sm"
|
|
213
|
-
variant="ghost"
|
|
214
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
215
|
-
>
|
|
199
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
216
200
|
删除
|
|
217
201
|
</Button>
|
|
218
202
|
</ItemActions>
|
|
@@ -261,26 +245,18 @@ export const ActionsSeparated: Story = {
|
|
|
261
245
|
<ItemDescription>点击操作按钮管理</ItemDescription>
|
|
262
246
|
</ItemContent>
|
|
263
247
|
<ItemActions separated>
|
|
264
|
-
<Button
|
|
265
|
-
size="sm"
|
|
266
|
-
variant="ghost"
|
|
267
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
268
|
-
>
|
|
248
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
269
249
|
<PencilIcon />
|
|
270
250
|
编辑
|
|
271
251
|
</Button>
|
|
272
|
-
<Button
|
|
273
|
-
size="sm"
|
|
274
|
-
variant="ghost"
|
|
275
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
276
|
-
>
|
|
252
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
277
253
|
<BookmarkIcon />
|
|
278
254
|
收藏
|
|
279
255
|
</Button>
|
|
280
256
|
<Button
|
|
281
257
|
size="sm"
|
|
282
|
-
variant="
|
|
283
|
-
className="text-destructive hover:
|
|
258
|
+
variant="link"
|
|
259
|
+
className="text-destructive hover:text-destructive/80"
|
|
284
260
|
>
|
|
285
261
|
<TrashIcon />
|
|
286
262
|
删除
|
|
@@ -352,11 +328,7 @@ export const GroupWithHeaderFooter: Story = {
|
|
|
352
328
|
</Item>
|
|
353
329
|
<ItemGroupFooter>
|
|
354
330
|
<span>共 2 条通知</span>
|
|
355
|
-
<Button
|
|
356
|
-
size="sm"
|
|
357
|
-
variant="ghost"
|
|
358
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
359
|
-
>
|
|
331
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
360
332
|
查看全部
|
|
361
333
|
</Button>
|
|
362
334
|
</ItemGroupFooter>
|
|
@@ -381,11 +353,7 @@ export const WithHeaderFooter: Story = {
|
|
|
381
353
|
</ItemContent>
|
|
382
354
|
<ItemFooter>
|
|
383
355
|
<span>截止:2025-01-15</span>
|
|
384
|
-
<Button
|
|
385
|
-
size="sm"
|
|
386
|
-
variant="ghost"
|
|
387
|
-
className="text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
388
|
-
>
|
|
356
|
+
<Button size="sm" variant="link" className="text-muted-foreground">
|
|
389
357
|
查看
|
|
390
358
|
</Button>
|
|
391
359
|
</ItemFooter>
|
|
@@ -60,7 +60,7 @@ function RadioGroupItem({ className, ...props }: RadioGroupItemProps) {
|
|
|
60
60
|
<RadioGroupPrimitive.Item
|
|
61
61
|
data-slot="radio-group-item"
|
|
62
62
|
className={cn(
|
|
63
|
-
'peer relative flex aspect-square size-4 shrink-0 cursor-pointer items-center justify-center rounded-full border border-input shadow-xs transition-[color,box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-
|
|
63
|
+
'peer relative flex aspect-square size-4 shrink-0 cursor-pointer items-center justify-center rounded-full border border-input shadow-xs transition-[color,box-shadow] outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:text-primary',
|
|
64
64
|
className,
|
|
65
65
|
)}
|
|
66
66
|
{...props}
|