@vvedantb/ui 0.1.1 → 0.1.2
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 -21
- package/package.json +3 -2
- package/src/ai-elements/artifact.tsx +99 -99
- package/src/ai-elements/attachments.tsx +366 -366
- package/src/ai-elements/chain-of-thought.tsx +212 -212
- package/src/ai-elements/code-block.tsx +478 -478
- package/src/ai-elements/conversation.tsx +157 -157
- package/src/ai-elements/image.tsx +25 -25
- package/src/ai-elements/inline-citation.tsx +252 -252
- package/src/ai-elements/message.tsx +269 -269
- package/src/ai-elements/model-selector.tsx +211 -211
- package/src/ai-elements/prompt-input.tsx +1018 -1018
- package/src/ai-elements/queue.tsx +251 -251
- package/src/ai-elements/reasoning.tsx +178 -178
- package/src/ai-elements/shimmer.tsx +56 -56
- package/src/ai-elements/sources.tsx +77 -77
- package/src/ai-elements/speech-input.tsx +290 -290
- package/src/ai-elements/suggestion.tsx +191 -191
- package/src/css.d.ts +2 -2
- package/src/index.ts +96 -96
- package/src/motion/presets.ts +35 -35
- package/src/motion/presets.vibot.ts +105 -105
- package/src/ui/_menu-classes.ts +30 -30
- package/src/ui/_menu-classes.vibot.ts +33 -33
- package/src/ui/accordion.tsx +81 -81
- package/src/ui/accordion.verve.tsx +56 -56
- package/src/ui/badge.tsx +34 -34
- package/src/ui/breadcrumb.tsx +83 -83
- package/src/ui/button-group.tsx +84 -84
- package/src/ui/button.tsx +57 -57
- package/src/ui/card.tsx +27 -27
- package/src/ui/carousel.tsx +211 -211
- package/src/ui/checkbox.tsx +27 -27
- package/src/ui/clear-input.tsx +170 -170
- package/src/ui/clear-input.vibot.tsx +76 -76
- package/src/ui/clearInputDissolve.ts +214 -214
- package/src/ui/collapsible.tsx +7 -7
- package/src/ui/command.tsx +148 -148
- package/src/ui/context-menu.tsx +173 -173
- package/src/ui/dialog.tsx +149 -149
- package/src/ui/dropdown-menu.tsx +178 -178
- package/src/ui/hover-card.tsx +31 -31
- package/src/ui/input-group.tsx +162 -162
- package/src/ui/input-group.verve.tsx +150 -150
- package/src/ui/input.tsx +21 -21
- package/src/ui/kbd.tsx +18 -18
- package/src/ui/label.tsx +20 -20
- package/src/ui/labeled-switch-row.tsx +53 -53
- package/src/ui/modalTransition.ts +85 -85
- package/src/ui/pagination.tsx +47 -47
- package/src/ui/popover.tsx +33 -33
- package/src/ui/progress.tsx +25 -25
- package/src/ui/radix-select.tsx +202 -202
- package/src/ui/scroll-area.tsx +55 -55
- package/src/ui/scroll-area.verve.tsx +43 -43
- package/src/ui/search-input.tsx +29 -29
- package/src/ui/select.tsx +163 -163
- package/src/ui/separator.tsx +28 -28
- package/src/ui/sheet.tsx +87 -87
- package/src/ui/skeleton.tsx +18 -18
- package/src/ui/sonner.css +16 -16
- package/src/ui/sonner.tsx +47 -47
- package/src/ui/spinner.tsx +27 -27
- package/src/ui/surface-classes.ts +55 -55
- package/src/ui/switch.tsx +26 -26
- package/src/ui/table.tsx +123 -123
- package/src/ui/tabs.tsx +140 -140
- package/src/ui/tabsSliding.ts +56 -56
- package/src/ui/textarea.tsx +21 -21
- package/src/ui/time-picker.tsx +170 -170
- package/src/ui/tooltip.tsx +25 -25
- package/src/ui/visually-hidden.tsx +3 -3
- package/src/utils/cn.ts +16 -16
- package/src/utils/ref.ts +10 -10
package/src/ui/clear-input.tsx
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { IconX } from "@tabler/icons-react";
|
|
5
|
-
import { cn } from "../utils/cn";
|
|
6
|
-
import { runClearInputDissolve } from "./clearInputDissolve";
|
|
7
|
-
|
|
8
|
-
export interface ClearInputProps extends Omit<
|
|
9
|
-
React.ComponentProps<"input">,
|
|
10
|
-
"value" | "onChange"
|
|
11
|
-
> {
|
|
12
|
-
value: string;
|
|
13
|
-
onChange: (value: string) => void;
|
|
14
|
-
wrapperClassName?: string;
|
|
15
|
-
/** Icon or adornment positioned in the field's left gutter. */
|
|
16
|
-
leading?: React.ReactNode;
|
|
17
|
-
clearLabel?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
|
|
21
|
-
(
|
|
22
|
-
{
|
|
23
|
-
value,
|
|
24
|
-
onChange,
|
|
25
|
-
wrapperClassName,
|
|
26
|
-
leading,
|
|
27
|
-
clearLabel = "Clear",
|
|
28
|
-
className,
|
|
29
|
-
placeholder = "",
|
|
30
|
-
...props
|
|
31
|
-
},
|
|
32
|
-
ref,
|
|
33
|
-
) => {
|
|
34
|
-
const wrapRef = React.useRef<HTMLDivElement>(null);
|
|
35
|
-
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
36
|
-
const mirrorRef = React.useRef<HTMLDivElement>(null);
|
|
37
|
-
const placeholderRef = React.useRef<HTMLDivElement>(null);
|
|
38
|
-
const glowRef = React.useRef<HTMLDivElement>(null);
|
|
39
|
-
const clearingRef = React.useRef(false);
|
|
40
|
-
const [clearing, setClearing] = React.useState(false);
|
|
41
|
-
|
|
42
|
-
function mergedRef(node: HTMLInputElement | null) {
|
|
43
|
-
inputRef.current = node;
|
|
44
|
-
if (typeof ref === "function") {
|
|
45
|
-
ref(node);
|
|
46
|
-
} else if (ref) {
|
|
47
|
-
ref.current = node;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const hasValue = value.length > 0 || clearing;
|
|
52
|
-
|
|
53
|
-
React.useEffect(() => {
|
|
54
|
-
const wrap = wrapRef.current;
|
|
55
|
-
const mirror = mirrorRef.current;
|
|
56
|
-
if (!wrap || !mirror || clearingRef.current) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
wrap.classList.toggle("has-value", value.length > 0);
|
|
60
|
-
if (value.length > 0) {
|
|
61
|
-
mirror.textContent = value.replace(/ /g, "\u00a0");
|
|
62
|
-
} else if (!clearingRef.current) {
|
|
63
|
-
mirror.textContent = "";
|
|
64
|
-
}
|
|
65
|
-
}, [value]);
|
|
66
|
-
|
|
67
|
-
const preventFocusSteal = (event: React.SyntheticEvent) => {
|
|
68
|
-
if (document.activeElement === inputRef.current) {
|
|
69
|
-
event.preventDefault();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const handleClear = () => {
|
|
74
|
-
const wrap = wrapRef.current;
|
|
75
|
-
const input = inputRef.current;
|
|
76
|
-
const mirror = mirrorRef.current;
|
|
77
|
-
const placeholderLayer = placeholderRef.current;
|
|
78
|
-
const glow = glowRef.current;
|
|
79
|
-
if (
|
|
80
|
-
!wrap ||
|
|
81
|
-
!input ||
|
|
82
|
-
!mirror ||
|
|
83
|
-
!placeholderLayer ||
|
|
84
|
-
!glow ||
|
|
85
|
-
clearingRef.current ||
|
|
86
|
-
value.length === 0
|
|
87
|
-
) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
clearingRef.current = true;
|
|
92
|
-
setClearing(true);
|
|
93
|
-
|
|
94
|
-
runClearInputDissolve(
|
|
95
|
-
{ wrap, input, mirror, placeholderLayer, glow },
|
|
96
|
-
() => {
|
|
97
|
-
onChange("");
|
|
98
|
-
},
|
|
99
|
-
() => {
|
|
100
|
-
clearingRef.current = false;
|
|
101
|
-
setClearing(false);
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const fieldClassName = cn(hasValue && "pr-9", className);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div
|
|
110
|
-
ref={wrapRef}
|
|
111
|
-
className={cn(
|
|
112
|
-
"t-clear",
|
|
113
|
-
hasValue && "has-value",
|
|
114
|
-
clearing && "is-clearing",
|
|
115
|
-
wrapperClassName,
|
|
116
|
-
)}
|
|
117
|
-
>
|
|
118
|
-
{leading}
|
|
119
|
-
<input
|
|
120
|
-
ref={mergedRef}
|
|
121
|
-
type="text"
|
|
122
|
-
value={value}
|
|
123
|
-
onChange={(event) => {
|
|
124
|
-
onChange(event.target.value);
|
|
125
|
-
}}
|
|
126
|
-
placeholder={placeholder}
|
|
127
|
-
className={cn(
|
|
128
|
-
"flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-base sm:text-sm transition-[border-color,box-shadow,background-color] duration-200 ease-smooth placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
129
|
-
fieldClassName,
|
|
130
|
-
)}
|
|
131
|
-
{...props}
|
|
132
|
-
/>
|
|
133
|
-
<div
|
|
134
|
-
ref={mirrorRef}
|
|
135
|
-
className={cn(
|
|
136
|
-
"t-clear-mirror border-0 bg-transparent text-foreground shadow-none",
|
|
137
|
-
fieldClassName,
|
|
138
|
-
)}
|
|
139
|
-
aria-hidden="true"
|
|
140
|
-
/>
|
|
141
|
-
<div
|
|
142
|
-
ref={placeholderRef}
|
|
143
|
-
className={cn(
|
|
144
|
-
"t-clear-placeholder border-0 bg-transparent text-field-placeholder shadow-none",
|
|
145
|
-
fieldClassName,
|
|
146
|
-
)}
|
|
147
|
-
aria-hidden="true"
|
|
148
|
-
>
|
|
149
|
-
{placeholder}
|
|
150
|
-
</div>
|
|
151
|
-
<div ref={glowRef} className="t-clear-glow" aria-hidden="true" />
|
|
152
|
-
{value.length > 0 && !clearing ? (
|
|
153
|
-
<button
|
|
154
|
-
type="button"
|
|
155
|
-
className="t-clear-btn absolute right-2 top-1/2 z-4 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.92] hover:bg-surface-tertiary/50 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring"
|
|
156
|
-
aria-label={clearLabel}
|
|
157
|
-
onPointerDown={preventFocusSteal}
|
|
158
|
-
onMouseDown={preventFocusSteal}
|
|
159
|
-
onClick={handleClear}
|
|
160
|
-
>
|
|
161
|
-
<IconX size={14} stroke={1.75} />
|
|
162
|
-
</button>
|
|
163
|
-
) : null}
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
},
|
|
167
|
-
);
|
|
168
|
-
ClearInput.displayName = "ClearInput";
|
|
169
|
-
|
|
170
|
-
export { ClearInput };
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { IconX } from "@tabler/icons-react";
|
|
5
|
+
import { cn } from "../utils/cn";
|
|
6
|
+
import { runClearInputDissolve } from "./clearInputDissolve";
|
|
7
|
+
|
|
8
|
+
export interface ClearInputProps extends Omit<
|
|
9
|
+
React.ComponentProps<"input">,
|
|
10
|
+
"value" | "onChange"
|
|
11
|
+
> {
|
|
12
|
+
value: string;
|
|
13
|
+
onChange: (value: string) => void;
|
|
14
|
+
wrapperClassName?: string;
|
|
15
|
+
/** Icon or adornment positioned in the field's left gutter. */
|
|
16
|
+
leading?: React.ReactNode;
|
|
17
|
+
clearLabel?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
|
|
21
|
+
(
|
|
22
|
+
{
|
|
23
|
+
value,
|
|
24
|
+
onChange,
|
|
25
|
+
wrapperClassName,
|
|
26
|
+
leading,
|
|
27
|
+
clearLabel = "Clear",
|
|
28
|
+
className,
|
|
29
|
+
placeholder = "",
|
|
30
|
+
...props
|
|
31
|
+
},
|
|
32
|
+
ref,
|
|
33
|
+
) => {
|
|
34
|
+
const wrapRef = React.useRef<HTMLDivElement>(null);
|
|
35
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
36
|
+
const mirrorRef = React.useRef<HTMLDivElement>(null);
|
|
37
|
+
const placeholderRef = React.useRef<HTMLDivElement>(null);
|
|
38
|
+
const glowRef = React.useRef<HTMLDivElement>(null);
|
|
39
|
+
const clearingRef = React.useRef(false);
|
|
40
|
+
const [clearing, setClearing] = React.useState(false);
|
|
41
|
+
|
|
42
|
+
function mergedRef(node: HTMLInputElement | null) {
|
|
43
|
+
inputRef.current = node;
|
|
44
|
+
if (typeof ref === "function") {
|
|
45
|
+
ref(node);
|
|
46
|
+
} else if (ref) {
|
|
47
|
+
ref.current = node;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const hasValue = value.length > 0 || clearing;
|
|
52
|
+
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
const wrap = wrapRef.current;
|
|
55
|
+
const mirror = mirrorRef.current;
|
|
56
|
+
if (!wrap || !mirror || clearingRef.current) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
wrap.classList.toggle("has-value", value.length > 0);
|
|
60
|
+
if (value.length > 0) {
|
|
61
|
+
mirror.textContent = value.replace(/ /g, "\u00a0");
|
|
62
|
+
} else if (!clearingRef.current) {
|
|
63
|
+
mirror.textContent = "";
|
|
64
|
+
}
|
|
65
|
+
}, [value]);
|
|
66
|
+
|
|
67
|
+
const preventFocusSteal = (event: React.SyntheticEvent) => {
|
|
68
|
+
if (document.activeElement === inputRef.current) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleClear = () => {
|
|
74
|
+
const wrap = wrapRef.current;
|
|
75
|
+
const input = inputRef.current;
|
|
76
|
+
const mirror = mirrorRef.current;
|
|
77
|
+
const placeholderLayer = placeholderRef.current;
|
|
78
|
+
const glow = glowRef.current;
|
|
79
|
+
if (
|
|
80
|
+
!wrap ||
|
|
81
|
+
!input ||
|
|
82
|
+
!mirror ||
|
|
83
|
+
!placeholderLayer ||
|
|
84
|
+
!glow ||
|
|
85
|
+
clearingRef.current ||
|
|
86
|
+
value.length === 0
|
|
87
|
+
) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clearingRef.current = true;
|
|
92
|
+
setClearing(true);
|
|
93
|
+
|
|
94
|
+
runClearInputDissolve(
|
|
95
|
+
{ wrap, input, mirror, placeholderLayer, glow },
|
|
96
|
+
() => {
|
|
97
|
+
onChange("");
|
|
98
|
+
},
|
|
99
|
+
() => {
|
|
100
|
+
clearingRef.current = false;
|
|
101
|
+
setClearing(false);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const fieldClassName = cn(hasValue && "pr-9", className);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
ref={wrapRef}
|
|
111
|
+
className={cn(
|
|
112
|
+
"t-clear",
|
|
113
|
+
hasValue && "has-value",
|
|
114
|
+
clearing && "is-clearing",
|
|
115
|
+
wrapperClassName,
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
{leading}
|
|
119
|
+
<input
|
|
120
|
+
ref={mergedRef}
|
|
121
|
+
type="text"
|
|
122
|
+
value={value}
|
|
123
|
+
onChange={(event) => {
|
|
124
|
+
onChange(event.target.value);
|
|
125
|
+
}}
|
|
126
|
+
placeholder={placeholder}
|
|
127
|
+
className={cn(
|
|
128
|
+
"flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-base sm:text-sm transition-[border-color,box-shadow,background-color] duration-200 ease-smooth placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
129
|
+
fieldClassName,
|
|
130
|
+
)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
<div
|
|
134
|
+
ref={mirrorRef}
|
|
135
|
+
className={cn(
|
|
136
|
+
"t-clear-mirror border-0 bg-transparent text-foreground shadow-none",
|
|
137
|
+
fieldClassName,
|
|
138
|
+
)}
|
|
139
|
+
aria-hidden="true"
|
|
140
|
+
/>
|
|
141
|
+
<div
|
|
142
|
+
ref={placeholderRef}
|
|
143
|
+
className={cn(
|
|
144
|
+
"t-clear-placeholder border-0 bg-transparent text-field-placeholder shadow-none",
|
|
145
|
+
fieldClassName,
|
|
146
|
+
)}
|
|
147
|
+
aria-hidden="true"
|
|
148
|
+
>
|
|
149
|
+
{placeholder}
|
|
150
|
+
</div>
|
|
151
|
+
<div ref={glowRef} className="t-clear-glow" aria-hidden="true" />
|
|
152
|
+
{value.length > 0 && !clearing ? (
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className="t-clear-btn absolute right-2 top-1/2 z-4 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-[background-color,color,transform] duration-100 ease-out active:scale-[0.92] hover:bg-surface-tertiary/50 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring"
|
|
156
|
+
aria-label={clearLabel}
|
|
157
|
+
onPointerDown={preventFocusSteal}
|
|
158
|
+
onMouseDown={preventFocusSteal}
|
|
159
|
+
onClick={handleClear}
|
|
160
|
+
>
|
|
161
|
+
<IconX size={14} stroke={1.75} />
|
|
162
|
+
</button>
|
|
163
|
+
) : null}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
ClearInput.displayName = "ClearInput";
|
|
169
|
+
|
|
170
|
+
export { ClearInput };
|
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { IconX } from "@tabler/icons-react";
|
|
4
|
-
import { cn } from "../utils/cn";
|
|
5
|
-
export interface ClearInputProps
|
|
6
|
-
extends Omit<React.ComponentProps<"input">, "value" | "onChange"> {
|
|
7
|
-
value: string;
|
|
8
|
-
onChange: (value: string) => void;
|
|
9
|
-
wrapperClassName?: string;
|
|
10
|
-
/** Icon or adornment positioned in the field's left gutter. */
|
|
11
|
-
leading?: React.ReactNode;
|
|
12
|
-
clearLabel?: string;
|
|
13
|
-
}
|
|
14
|
-
const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
|
|
15
|
-
(
|
|
16
|
-
{
|
|
17
|
-
value,
|
|
18
|
-
onChange,
|
|
19
|
-
wrapperClassName,
|
|
20
|
-
leading,
|
|
21
|
-
clearLabel = "Clear",
|
|
22
|
-
className,
|
|
23
|
-
placeholder,
|
|
24
|
-
...props
|
|
25
|
-
},
|
|
26
|
-
ref,
|
|
27
|
-
) => {
|
|
28
|
-
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
29
|
-
const mergedRef = (node: HTMLInputElement | null) => {
|
|
30
|
-
inputRef.current = node;
|
|
31
|
-
if (typeof ref === "function") {
|
|
32
|
-
ref(node);
|
|
33
|
-
} else if (ref) {
|
|
34
|
-
ref.current = node;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
const hasValue = value.length > 0;
|
|
38
|
-
return (
|
|
39
|
-
<div className={cn("relative", wrapperClassName)}>
|
|
40
|
-
{leading}
|
|
41
|
-
<input
|
|
42
|
-
ref={mergedRef}
|
|
43
|
-
type="text"
|
|
44
|
-
value={value}
|
|
45
|
-
onChange={(event) => {
|
|
46
|
-
onChange(event.target.value);
|
|
47
|
-
}}
|
|
48
|
-
placeholder={placeholder}
|
|
49
|
-
className={cn(
|
|
50
|
-
"flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
51
|
-
"pr-9",
|
|
52
|
-
className,
|
|
53
|
-
)}
|
|
54
|
-
{...props}
|
|
55
|
-
/>
|
|
56
|
-
<button
|
|
57
|
-
type="button"
|
|
58
|
-
tabIndex={hasValue ? 0 : -1}
|
|
59
|
-
className={cn(
|
|
60
|
-
"absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-['']",
|
|
61
|
-
hasValue ? "opacity-100" : "pointer-events-none opacity-0",
|
|
62
|
-
)}
|
|
63
|
-
aria-hidden={!hasValue}
|
|
64
|
-
aria-label={clearLabel}
|
|
65
|
-
onClick={() => {
|
|
66
|
-
onChange("");
|
|
67
|
-
inputRef.current?.focus({ preventScroll: true });
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
<IconX size={14} stroke={1.75} />
|
|
71
|
-
</button>
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
export { ClearInput };
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { IconX } from "@tabler/icons-react";
|
|
4
|
+
import { cn } from "../utils/cn";
|
|
5
|
+
export interface ClearInputProps
|
|
6
|
+
extends Omit<React.ComponentProps<"input">, "value" | "onChange"> {
|
|
7
|
+
value: string;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
wrapperClassName?: string;
|
|
10
|
+
/** Icon or adornment positioned in the field's left gutter. */
|
|
11
|
+
leading?: React.ReactNode;
|
|
12
|
+
clearLabel?: string;
|
|
13
|
+
}
|
|
14
|
+
const ClearInput = React.forwardRef<HTMLInputElement, ClearInputProps>(
|
|
15
|
+
(
|
|
16
|
+
{
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
wrapperClassName,
|
|
20
|
+
leading,
|
|
21
|
+
clearLabel = "Clear",
|
|
22
|
+
className,
|
|
23
|
+
placeholder,
|
|
24
|
+
...props
|
|
25
|
+
},
|
|
26
|
+
ref,
|
|
27
|
+
) => {
|
|
28
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
29
|
+
const mergedRef = (node: HTMLInputElement | null) => {
|
|
30
|
+
inputRef.current = node;
|
|
31
|
+
if (typeof ref === "function") {
|
|
32
|
+
ref(node);
|
|
33
|
+
} else if (ref) {
|
|
34
|
+
ref.current = node;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const hasValue = value.length > 0;
|
|
38
|
+
return (
|
|
39
|
+
<div className={cn("relative", wrapperClassName)}>
|
|
40
|
+
{leading}
|
|
41
|
+
<input
|
|
42
|
+
ref={mergedRef}
|
|
43
|
+
type="text"
|
|
44
|
+
value={value}
|
|
45
|
+
onChange={(event) => {
|
|
46
|
+
onChange(event.target.value);
|
|
47
|
+
}}
|
|
48
|
+
placeholder={placeholder}
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex h-10 w-full rounded-field border border-border bg-field-background px-3.5 py-2 text-sm transition-[border-color,background-color] duration-200 ease-smooth placeholder:text-field-placeholder focus-visible:border-focus-border focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
51
|
+
"pr-9",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
tabIndex={hasValue ? 0 : -1}
|
|
59
|
+
className={cn(
|
|
60
|
+
"absolute right-2 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted transition-[opacity,background-color,color,transform] duration-150 ease-out active:duration-100 active:ease-out active:scale-[0.97] hover:bg-surface-tertiary hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring before:absolute before:inset-[-8px] before:content-['']",
|
|
61
|
+
hasValue ? "opacity-100" : "pointer-events-none opacity-0",
|
|
62
|
+
)}
|
|
63
|
+
aria-hidden={!hasValue}
|
|
64
|
+
aria-label={clearLabel}
|
|
65
|
+
onClick={() => {
|
|
66
|
+
onChange("");
|
|
67
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<IconX size={14} stroke={1.75} />
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
export { ClearInput };
|