@eggspot/ui 0.0.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/eslint.config.js +4 -0
- package/package.json +66 -0
- package/postcss.config.mjs +1 -0
- package/src/components/Button.machine.tsx +50 -0
- package/src/components/Button.tsx +249 -0
- package/src/components/Button.variants.tsx +186 -0
- package/src/components/ButtonGroup.tsx +56 -0
- package/src/components/Calendar.tsx +275 -0
- package/src/components/Calendar.utils.tsx +22 -0
- package/src/components/Checkbox.tsx +199 -0
- package/src/components/ConfirmDialog.tsx +183 -0
- package/src/components/DashboardLayout/DashboardLayout.tsx +348 -0
- package/src/components/DashboardLayout/SidebarNav.tsx +509 -0
- package/src/components/DashboardLayout/index.ts +33 -0
- package/src/components/DataTable/DataTable.tsx +557 -0
- package/src/components/DataTable/DataTableColumnHeader.tsx +122 -0
- package/src/components/DataTable/DataTableDisplaySettings.tsx +265 -0
- package/src/components/DataTable/DataTableFloatingBar.tsx +44 -0
- package/src/components/DataTable/DataTablePagination.tsx +168 -0
- package/src/components/DataTable/DataTableStates.tsx +69 -0
- package/src/components/DataTable/DataTableToolbarContainer.tsx +47 -0
- package/src/components/DataTable/hooks/use-data-table-settings.ts +101 -0
- package/src/components/DataTable/index.ts +7 -0
- package/src/components/DataTable/types/data-table.ts +97 -0
- package/src/components/DatePicker.tsx +213 -0
- package/src/components/DatePicker.utils.tsx +38 -0
- package/src/components/Datefield.tsx +109 -0
- package/src/components/Datefield.utils.ts +10 -0
- package/src/components/Dialog.tsx +167 -0
- package/src/components/Field.tsx +49 -0
- package/src/components/Filter/Filter.store.tsx +122 -0
- package/src/components/Filter/Filter.tsx +11 -0
- package/src/components/Filter/Filter.types.ts +107 -0
- package/src/components/Filter/FilterBar.tsx +38 -0
- package/src/components/Filter/FilterBuilder.tsx +158 -0
- package/src/components/Filter/FilterField/DateModeRowValue.tsx +250 -0
- package/src/components/Filter/FilterField/FilterAsyncSelect.tsx +191 -0
- package/src/components/Filter/FilterField/FilterDateMode.tsx +241 -0
- package/src/components/Filter/FilterField/FilterDateRange.tsx +169 -0
- package/src/components/Filter/FilterField/FilterSelect.tsx +208 -0
- package/src/components/Filter/FilterField/FilterSingleDate.tsx +277 -0
- package/src/components/Filter/FilterField/OptionItem.tsx +112 -0
- package/src/components/Filter/FilterField/index.ts +6 -0
- package/src/components/Filter/FilterRow.tsx +527 -0
- package/src/components/Filter/index.ts +17 -0
- package/src/components/Form.tsx +195 -0
- package/src/components/Heading.tsx +41 -0
- package/src/components/Input.tsx +221 -0
- package/src/components/InputOTP.tsx +78 -0
- package/src/components/Label.tsx +65 -0
- package/src/components/Layout.tsx +129 -0
- package/src/components/ListBox.tsx +97 -0
- package/src/components/Menu.tsx +152 -0
- package/src/components/NativeSelect.tsx +77 -0
- package/src/components/NumberInput.tsx +114 -0
- package/src/components/Popover.tsx +44 -0
- package/src/components/Provider.tsx +22 -0
- package/src/components/RadioGroup.tsx +191 -0
- package/src/components/Resizable.tsx +71 -0
- package/src/components/ScrollArea.tsx +57 -0
- package/src/components/Select.tsx +626 -0
- package/src/components/Select.utils.tsx +64 -0
- package/src/components/Separator.tsx +25 -0
- package/src/components/Sheet.tsx +147 -0
- package/src/components/Sonner.tsx +96 -0
- package/src/components/Spinner.tsx +30 -0
- package/src/components/Switch.tsx +51 -0
- package/src/components/Text.tsx +35 -0
- package/src/components/Tooltip.tsx +58 -0
- package/src/consts/config.ts +2 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/lib/utils.ts +10 -0
- package/tsconfig.json +11 -0
- package/tsconfig.lint.json +8 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Label } from "@eggspot/ui/components/Label"
|
|
5
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
6
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
7
|
+
import { LabelProps } from "react-aria-components"
|
|
8
|
+
import {
|
|
9
|
+
Controller,
|
|
10
|
+
FormProvider,
|
|
11
|
+
useFormContext,
|
|
12
|
+
UseFormReturn,
|
|
13
|
+
useFormState,
|
|
14
|
+
type ControllerProps,
|
|
15
|
+
type FieldPath,
|
|
16
|
+
type FieldValues,
|
|
17
|
+
} from "react-hook-form"
|
|
18
|
+
|
|
19
|
+
const Form = FormProvider
|
|
20
|
+
|
|
21
|
+
type FormFieldContextValue<
|
|
22
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
23
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
24
|
+
> = {
|
|
25
|
+
name: TName
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
29
|
+
{} as FormFieldContextValue
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const FormField = <
|
|
33
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
34
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
35
|
+
>({
|
|
36
|
+
...props
|
|
37
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
38
|
+
return (
|
|
39
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
40
|
+
<Controller {...props} />
|
|
41
|
+
</FormFieldContext.Provider>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const useFormField = () => {
|
|
46
|
+
const fieldContext = React.useContext(FormFieldContext)
|
|
47
|
+
const itemContext = React.useContext(FormItemContext)
|
|
48
|
+
const { getFieldState } = useFormContext()
|
|
49
|
+
const formState = useFormState({ name: fieldContext.name })
|
|
50
|
+
const fieldState = getFieldState(fieldContext.name, formState)
|
|
51
|
+
|
|
52
|
+
if (!fieldContext) {
|
|
53
|
+
throw new Error("useFormField should be used within <FormField>")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { id } = itemContext
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
name: fieldContext.name,
|
|
61
|
+
formItemId: `${id}-form-item`,
|
|
62
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
63
|
+
formMessageId: `${id}-form-item-message`,
|
|
64
|
+
...fieldState,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type FormItemContextValue = {
|
|
69
|
+
id: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
73
|
+
{} as FormItemContextValue
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
function FormItem({
|
|
77
|
+
className,
|
|
78
|
+
flow = "column",
|
|
79
|
+
...props
|
|
80
|
+
}: React.ComponentProps<"div"> & { flow?: "row" | "column" }) {
|
|
81
|
+
const id = React.useId()
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<FormItemContext.Provider value={{ id }}>
|
|
85
|
+
<div
|
|
86
|
+
data-slot="form-item"
|
|
87
|
+
className={cn(
|
|
88
|
+
"flex gap-2",
|
|
89
|
+
flow === "column" && "flex-col",
|
|
90
|
+
flow === "row" && "flex-row items-center",
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
</FormItemContext.Provider>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function FormLabel({ className, ...props }: LabelProps) {
|
|
100
|
+
const { error, formItemId } = useFormField()
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<Label
|
|
104
|
+
data-slot="form-label"
|
|
105
|
+
data-error={!!error}
|
|
106
|
+
className={cn("whitespace-nowrap", className)}
|
|
107
|
+
htmlFor={formItemId}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|
114
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
115
|
+
|
|
116
|
+
const ariaProps = {
|
|
117
|
+
isInvalid: !!error,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Slot
|
|
122
|
+
data-slot="form-control"
|
|
123
|
+
id={formItemId}
|
|
124
|
+
aria-describedby={
|
|
125
|
+
!error
|
|
126
|
+
? `${formDescriptionId}`
|
|
127
|
+
: `${formDescriptionId} ${formMessageId}`
|
|
128
|
+
}
|
|
129
|
+
aria-invalid={!!error}
|
|
130
|
+
{...ariaProps}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
137
|
+
const { formDescriptionId } = useFormField()
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<p
|
|
141
|
+
data-slot="form-description"
|
|
142
|
+
id={formDescriptionId}
|
|
143
|
+
className={cn("text-gray-11 my-0 text-xs", className)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
150
|
+
const { error, formMessageId } = useFormField()
|
|
151
|
+
const body = error ? String(error?.message ?? "") : props.children
|
|
152
|
+
|
|
153
|
+
if (!body) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<p
|
|
159
|
+
data-slot="form-message"
|
|
160
|
+
id={formMessageId}
|
|
161
|
+
className={cn("text-error-11 my-0 text-xs", className)}
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
{body}
|
|
165
|
+
</p>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setSubmitErrors(
|
|
170
|
+
form: UseFormReturn<any>,
|
|
171
|
+
error: Record<string, string>
|
|
172
|
+
) {
|
|
173
|
+
try {
|
|
174
|
+
Object.entries(error).forEach(([key, value]) => {
|
|
175
|
+
form.setError(key, { message: value })
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// focus the first error
|
|
179
|
+
form.setFocus(Object.keys(error)[0] as string)
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(error)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
Form,
|
|
187
|
+
FormControl,
|
|
188
|
+
FormDescription,
|
|
189
|
+
FormField,
|
|
190
|
+
FormItem,
|
|
191
|
+
FormLabel,
|
|
192
|
+
FormMessage,
|
|
193
|
+
setSubmitErrors,
|
|
194
|
+
useFormField,
|
|
195
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
2
|
+
import { cva, VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
const headingVariants = cva("font-semibold", {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
h1: "text-3xl",
|
|
9
|
+
h2: "text-2xl",
|
|
10
|
+
h3: "text-xl",
|
|
11
|
+
h4: "text-lg",
|
|
12
|
+
h5: "text-md",
|
|
13
|
+
h6: "text-base",
|
|
14
|
+
subtitle1: "text-sm",
|
|
15
|
+
subtitle2: "text-xs",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "h1",
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
interface HeadingProps
|
|
24
|
+
extends React.ComponentProps<"h1">,
|
|
25
|
+
VariantProps<typeof headingVariants> {
|
|
26
|
+
asChild?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function Heading({
|
|
30
|
+
asChild = false,
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
...props
|
|
34
|
+
}: HeadingProps) {
|
|
35
|
+
const defaultElement = (variant?.startsWith("h") ? variant : "p") as any
|
|
36
|
+
const Comp = asChild ? Slot.Root : defaultElement
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Comp className={cn(headingVariants({ variant, className }))} {...props} />
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react"
|
|
4
|
+
import { Button } from "@eggspot/ui/components/Button"
|
|
5
|
+
import { FieldGroup } from "@eggspot/ui/components/Field"
|
|
6
|
+
import { Label } from "@eggspot/ui/components/Label"
|
|
7
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
8
|
+
import { EyeIcon, EyeOffIcon, SearchIcon, XIcon } from "lucide-react"
|
|
9
|
+
import {
|
|
10
|
+
Input as AriaInput,
|
|
11
|
+
SearchField as AriaSearchField,
|
|
12
|
+
SearchFieldProps as AriaSearchFieldProps,
|
|
13
|
+
TextArea as AriaTextArea,
|
|
14
|
+
TextField as AriaTextField,
|
|
15
|
+
TextFieldProps as AriaTextFieldProps,
|
|
16
|
+
} from "react-aria-components"
|
|
17
|
+
import { useDebounce } from "react-use"
|
|
18
|
+
|
|
19
|
+
interface InputProps extends AriaTextFieldProps {
|
|
20
|
+
label?: string
|
|
21
|
+
withAsterisk?: boolean
|
|
22
|
+
tooltip?: React.ReactNode
|
|
23
|
+
placeholder?: string
|
|
24
|
+
leftIcon?: React.ReactNode
|
|
25
|
+
rightIcon?: React.ReactNode
|
|
26
|
+
ref?: React.RefCallback<HTMLInputElement>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Input({
|
|
30
|
+
className,
|
|
31
|
+
label,
|
|
32
|
+
withAsterisk,
|
|
33
|
+
tooltip,
|
|
34
|
+
leftIcon,
|
|
35
|
+
rightIcon,
|
|
36
|
+
ref,
|
|
37
|
+
...props
|
|
38
|
+
}: InputProps) {
|
|
39
|
+
return (
|
|
40
|
+
<AriaTextField
|
|
41
|
+
className={"flex flex-col gap-1.5"}
|
|
42
|
+
isInvalid={(props as any)["aria-invalid"]}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{label && (
|
|
46
|
+
<Label withAsterisk={withAsterisk} tooltip={tooltip}>
|
|
47
|
+
{label}
|
|
48
|
+
</Label>
|
|
49
|
+
)}
|
|
50
|
+
<FieldGroup className={cn("py-0", className)}>
|
|
51
|
+
{leftIcon && <div className="pr-2">{leftIcon}</div>}
|
|
52
|
+
<AriaInput
|
|
53
|
+
ref={ref}
|
|
54
|
+
className="placeholder:text-gray-11 h-full flex-1 focus-visible:outline-none"
|
|
55
|
+
/>
|
|
56
|
+
{rightIcon && <div className="pl-2">{rightIcon}</div>}
|
|
57
|
+
</FieldGroup>
|
|
58
|
+
</AriaTextField>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface PasswordInputProps extends AriaTextFieldProps {
|
|
63
|
+
placeholder?: string
|
|
64
|
+
label?: string
|
|
65
|
+
withAsterisk?: boolean
|
|
66
|
+
tooltip?: React.ReactNode
|
|
67
|
+
ref?: React.RefCallback<HTMLInputElement>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function PasswordInput({
|
|
71
|
+
className,
|
|
72
|
+
label,
|
|
73
|
+
withAsterisk,
|
|
74
|
+
tooltip,
|
|
75
|
+
ref,
|
|
76
|
+
...props
|
|
77
|
+
}: PasswordInputProps) {
|
|
78
|
+
const [isVisible, setIsVisible] = React.useState(false)
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<AriaTextField className={cn("flex flex-col gap-1.5")} {...props}>
|
|
82
|
+
{label && (
|
|
83
|
+
<Label withAsterisk={withAsterisk} tooltip={tooltip}>
|
|
84
|
+
{label}
|
|
85
|
+
</Label>
|
|
86
|
+
)}
|
|
87
|
+
<FieldGroup className={cn("py-0 pr-1", className)}>
|
|
88
|
+
<AriaInput
|
|
89
|
+
ref={ref}
|
|
90
|
+
type={isVisible ? "text" : "password"}
|
|
91
|
+
className="placeholder:text-gray-11 h-full flex-1 focus-visible:outline-none"
|
|
92
|
+
/>
|
|
93
|
+
<Button
|
|
94
|
+
size="sm"
|
|
95
|
+
mode="icon"
|
|
96
|
+
variant="ghost"
|
|
97
|
+
intent="secondary"
|
|
98
|
+
onClick={() => setIsVisible(!isVisible)}
|
|
99
|
+
tooltip="Toggle password visibility"
|
|
100
|
+
tooltipDelay={1000}
|
|
101
|
+
className="rounded-sm"
|
|
102
|
+
>
|
|
103
|
+
{isVisible ? <EyeIcon /> : <EyeOffIcon />}
|
|
104
|
+
</Button>
|
|
105
|
+
</FieldGroup>
|
|
106
|
+
</AriaTextField>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface TextAreaProps extends AriaTextFieldProps {
|
|
111
|
+
label?: string
|
|
112
|
+
withAsterisk?: boolean
|
|
113
|
+
tooltip?: React.ReactNode
|
|
114
|
+
placeholder?: string
|
|
115
|
+
ref?: React.RefCallback<HTMLTextAreaElement>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function TextArea({
|
|
119
|
+
className,
|
|
120
|
+
label,
|
|
121
|
+
withAsterisk,
|
|
122
|
+
tooltip,
|
|
123
|
+
ref,
|
|
124
|
+
...props
|
|
125
|
+
}: TextAreaProps) {
|
|
126
|
+
return (
|
|
127
|
+
<AriaTextField className={cn("grid gap-1.5", className)} {...props}>
|
|
128
|
+
{label && (
|
|
129
|
+
<Label withAsterisk={withAsterisk} tooltip={tooltip}>
|
|
130
|
+
{label}
|
|
131
|
+
</Label>
|
|
132
|
+
)}
|
|
133
|
+
<AriaTextArea
|
|
134
|
+
ref={ref}
|
|
135
|
+
className={cn(
|
|
136
|
+
"bg-gray-2 ring-offset-gray-1 placeholder:text-gray-11 flex field-sizing-content min-h-[80px] w-full rounded-md px-3 py-2 md:text-sm",
|
|
137
|
+
"ring-gray-7 ring",
|
|
138
|
+
/* Focus Within */
|
|
139
|
+
"data-[focused]:ring-accent-9 transition-all data-[focused]:ring-2",
|
|
140
|
+
/* Disabled */
|
|
141
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-60",
|
|
142
|
+
/* Resets */
|
|
143
|
+
"focus-visible:outline-none",
|
|
144
|
+
/* Invalid */
|
|
145
|
+
"data-[invalid]:ring-error-9 aria-invalid:ring-error-9",
|
|
146
|
+
className
|
|
147
|
+
)}
|
|
148
|
+
/>
|
|
149
|
+
</AriaTextField>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface SearchInputProps extends AriaSearchFieldProps {
|
|
154
|
+
placeholder?: string
|
|
155
|
+
ref?: React.RefCallback<HTMLInputElement>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function SearchInput({ className, ref, ...props }: SearchInputProps) {
|
|
159
|
+
return (
|
|
160
|
+
<AriaSearchField {...props} aria-label="Search" className="group">
|
|
161
|
+
<FieldGroup className={cn("py-0 pr-1", className)}>
|
|
162
|
+
<div className="pr-2">
|
|
163
|
+
<SearchIcon aria-hidden className="text-gray-11 size-4" />
|
|
164
|
+
</div>
|
|
165
|
+
<AriaInput
|
|
166
|
+
ref={ref}
|
|
167
|
+
aria-label="Search"
|
|
168
|
+
className="placeholder:text-gray-11 h-full flex-1 focus-visible:outline-none [&::-webkit-search-cancel-button]:hidden"
|
|
169
|
+
/>
|
|
170
|
+
<Button
|
|
171
|
+
size="sm"
|
|
172
|
+
mode="icon"
|
|
173
|
+
variant="ghost"
|
|
174
|
+
tooltip="Clear"
|
|
175
|
+
className={cn(
|
|
176
|
+
"size-6 rounded-sm",
|
|
177
|
+
/* Hover */
|
|
178
|
+
"data-[hovered]:opacity-100",
|
|
179
|
+
/* Disabled */
|
|
180
|
+
"data-[disabled]:pointer-events-none",
|
|
181
|
+
/* Empty */
|
|
182
|
+
"group-data-[empty]:invisible"
|
|
183
|
+
)}
|
|
184
|
+
>
|
|
185
|
+
<XIcon />
|
|
186
|
+
</Button>
|
|
187
|
+
</FieldGroup>
|
|
188
|
+
</AriaSearchField>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface DebouncedSearchInputProps extends SearchInputProps {
|
|
193
|
+
value: string
|
|
194
|
+
onChange: (value: string) => void
|
|
195
|
+
placeholder?: string
|
|
196
|
+
debounceTime?: number
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function DebouncedSearchInput({
|
|
200
|
+
value,
|
|
201
|
+
onChange,
|
|
202
|
+
placeholder = "Search...",
|
|
203
|
+
debounceTime = 200,
|
|
204
|
+
...props
|
|
205
|
+
}: DebouncedSearchInputProps) {
|
|
206
|
+
const [internalValue, setInternalValue] = useState(value)
|
|
207
|
+
|
|
208
|
+
useDebounce(() => onChange(internalValue), debounceTime, [internalValue])
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<SearchInput
|
|
212
|
+
value={internalValue}
|
|
213
|
+
onChange={setInternalValue}
|
|
214
|
+
placeholder={placeholder}
|
|
215
|
+
{...props}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export { DebouncedSearchInput, Input, PasswordInput, SearchInput, TextArea }
|
|
221
|
+
export type { InputProps, PasswordInputProps, SearchInputProps }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
5
|
+
import { OTPInput, OTPInputContext } from "input-otp"
|
|
6
|
+
import { MinusIcon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
function InputOTP({
|
|
9
|
+
className,
|
|
10
|
+
containerClassName,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof OTPInput> & {
|
|
13
|
+
containerClassName?: string
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<OTPInput
|
|
17
|
+
data-slot="input-otp"
|
|
18
|
+
containerClassName={cn(
|
|
19
|
+
"group flex items-center gap-2 has-disabled:opacity-50",
|
|
20
|
+
containerClassName
|
|
21
|
+
)}
|
|
22
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="input-otp-group"
|
|
32
|
+
className={cn("flex items-center gap-2", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function InputOTPSlot({
|
|
39
|
+
index,
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<"div"> & {
|
|
43
|
+
index: number
|
|
44
|
+
}) {
|
|
45
|
+
const inputOTPContext = React.useContext(OTPInputContext)
|
|
46
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="input-otp-slot"
|
|
51
|
+
data-active={isActive}
|
|
52
|
+
className={cn(
|
|
53
|
+
"ring-gray-7 bg-gray-2 relative flex size-9 items-center justify-center rounded-md shadow-sm ring transition-all outline-none sm:size-10",
|
|
54
|
+
/* Active */
|
|
55
|
+
"data-[active=true]:ring-accent-9 data-[active=true]:z-10 data-[active=true]:ring-2",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{char}
|
|
61
|
+
{hasFakeCaret && (
|
|
62
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
63
|
+
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|
71
|
+
return (
|
|
72
|
+
<div data-slot="input-otp-separator" role="separator" {...props}>
|
|
73
|
+
<MinusIcon />
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip, TooltipTrigger } from "@eggspot/ui/components/Tooltip"
|
|
4
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
5
|
+
import { cva } from "class-variance-authority"
|
|
6
|
+
import { InfoIcon } from "lucide-react"
|
|
7
|
+
import {
|
|
8
|
+
Label as AriaLabel,
|
|
9
|
+
LabelProps as AriaLabelProps,
|
|
10
|
+
Focusable,
|
|
11
|
+
} from "react-aria-components"
|
|
12
|
+
|
|
13
|
+
import { Button } from "./Button"
|
|
14
|
+
import { Popover, PopoverDialog, PopoverTrigger } from "./Popover"
|
|
15
|
+
|
|
16
|
+
const labelVariants = cva([
|
|
17
|
+
"flex items-center gap-1 text-sm font-medium",
|
|
18
|
+
/* Disabled */
|
|
19
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70",
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
interface LabelProps extends AriaLabelProps {
|
|
23
|
+
withAsterisk?: boolean
|
|
24
|
+
tooltip?: React.ReactNode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Label({
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
withAsterisk,
|
|
31
|
+
tooltip,
|
|
32
|
+
...props
|
|
33
|
+
}: LabelProps) {
|
|
34
|
+
return (
|
|
35
|
+
<AriaLabel className={cn(labelVariants(), className)} {...props}>
|
|
36
|
+
{children}
|
|
37
|
+
{withAsterisk && <span className="text-error-9"> *</span>}
|
|
38
|
+
{tooltip && (
|
|
39
|
+
<>
|
|
40
|
+
<TooltipTrigger delay={0}>
|
|
41
|
+
<Focusable>
|
|
42
|
+
<InfoIcon className="text-gray-11 size-[14px] outline-none pointer-coarse:hidden" />
|
|
43
|
+
</Focusable>
|
|
44
|
+
<Tooltip>{tooltip}</Tooltip>
|
|
45
|
+
</TooltipTrigger>
|
|
46
|
+
<PopoverTrigger>
|
|
47
|
+
<Button
|
|
48
|
+
size="sm"
|
|
49
|
+
mode="icon"
|
|
50
|
+
variant="ghost"
|
|
51
|
+
className="-translate-x-1 pointer-fine:hidden"
|
|
52
|
+
>
|
|
53
|
+
<InfoIcon className="text-gray-11 size-[14px]! outline-none" />
|
|
54
|
+
</Button>
|
|
55
|
+
<Popover>
|
|
56
|
+
<PopoverDialog className="p-2 text-sm">{tooltip}</PopoverDialog>
|
|
57
|
+
</Popover>
|
|
58
|
+
</PopoverTrigger>
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
</AriaLabel>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Label, labelVariants }
|