@alpic-ai/ui 0.0.0-dev.4a35dc7 → 0.0.0-dev.6953a69
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/dist/components/accordion-card.d.mts +1 -1
- package/dist/components/accordion.d.mts +1 -1
- package/dist/components/alert.d.mts +1 -1
- package/dist/components/attachment-tile.mjs +1 -1
- package/dist/components/avatar.d.mts +1 -1
- package/dist/components/breadcrumb.d.mts +1 -1
- package/dist/components/button.d.mts +1 -1
- package/dist/components/card.d.mts +1 -1
- package/dist/components/checkbox.d.mts +1 -1
- package/dist/components/collapsible.d.mts +1 -1
- package/dist/components/combobox.d.mts +1 -1
- package/dist/components/combobox.mjs +2 -2
- package/dist/components/command.d.mts +1 -1
- package/dist/components/copyable.d.mts +1 -1
- package/dist/components/copyable.mjs +1 -1
- package/dist/components/description-list.d.mts +1 -1
- package/dist/components/dialog.d.mts +1 -1
- package/dist/components/dialog.mjs +2 -2
- package/dist/components/dropdown-menu.d.mts +1 -1
- package/dist/components/form.d.mts +119 -0
- package/dist/components/form.mjs +192 -0
- package/dist/components/input-group.d.mts +1 -1
- package/dist/components/input.d.mts +3 -1
- package/dist/components/input.mjs +20 -11
- package/dist/components/label.d.mts +1 -1
- package/dist/components/pagination.d.mts +1 -1
- package/dist/components/popover.d.mts +1 -1
- package/dist/components/radio-group.d.mts +1 -1
- package/dist/components/scroll-area.d.mts +1 -1
- package/dist/components/select-trigger-variants.mjs +1 -0
- package/dist/components/select.d.mts +2 -10
- package/dist/components/select.mjs +8 -28
- package/dist/components/separator.d.mts +1 -1
- package/dist/components/sheet.d.mts +1 -1
- package/dist/components/sidebar.d.mts +1 -1
- package/dist/components/sidebar.mjs +8 -8
- package/dist/components/sonner.d.mts +1 -1
- package/dist/components/switch.d.mts +1 -1
- package/dist/components/table.d.mts +1 -1
- package/dist/components/tabs.d.mts +1 -1
- package/dist/components/tabs.mjs +1 -1
- package/dist/components/textarea.d.mts +3 -1
- package/dist/components/textarea.mjs +20 -11
- package/dist/components/toggle-group.d.mts +1 -1
- package/dist/components/toggle-group.mjs +1 -1
- package/dist/components/tooltip-icon-button.mjs +1 -1
- package/dist/components/tooltip.d.mts +1 -1
- package/package.json +10 -11
- package/src/components/combobox.tsx +1 -1
- package/src/components/dialog.tsx +2 -2
- package/src/components/form.tsx +343 -0
- package/src/components/input.tsx +12 -0
- package/src/components/select-trigger-variants.ts +1 -0
- package/src/components/select.tsx +2 -35
- package/src/components/sidebar.tsx +8 -10
- package/src/components/textarea.tsx +12 -1
- package/src/stories/accordion-card.stories.tsx +53 -0
- package/src/stories/accordion.stories.tsx +65 -0
- package/src/stories/alert.stories.tsx +58 -0
- package/src/stories/attachment-tile.stories.tsx +37 -0
- package/src/stories/avatar.stories.tsx +54 -0
- package/src/stories/badge.stories.tsx +50 -0
- package/src/stories/breadcrumb.stories.tsx +107 -0
- package/src/stories/button.stories.tsx +342 -0
- package/src/stories/card.stories.tsx +89 -0
- package/src/stories/checkbox.stories.tsx +56 -0
- package/src/stories/collapsible.stories.tsx +69 -0
- package/src/stories/combobox.stories.tsx +214 -0
- package/src/stories/command.stories.tsx +95 -0
- package/src/stories/copyable.stories.tsx +29 -0
- package/src/stories/description-list.stories.tsx +71 -0
- package/src/stories/dialog.stories.tsx +135 -0
- package/src/stories/dropdown-menu.stories.tsx +191 -0
- package/src/stories/form.stories.tsx +91 -0
- package/src/stories/input-group.stories.tsx +63 -0
- package/src/stories/input.stories.tsx +72 -0
- package/src/stories/label.stories.tsx +26 -0
- package/src/stories/ladle.css +3 -0
- package/src/stories/pagination.stories.tsx +35 -0
- package/src/stories/popover.stories.tsx +34 -0
- package/src/stories/radio-group.stories.tsx +59 -0
- package/src/stories/scroll-area.stories.tsx +43 -0
- package/src/stories/select.stories.tsx +95 -0
- package/src/stories/separator.stories.tsx +36 -0
- package/src/stories/sheet.stories.tsx +76 -0
- package/src/stories/sidebar.stories.tsx +255 -0
- package/src/stories/skeleton.stories.tsx +47 -0
- package/src/stories/sonner.stories.tsx +91 -0
- package/src/stories/spinner.stories.tsx +66 -0
- package/src/stories/status-dot.stories.tsx +27 -0
- package/src/stories/switch.stories.tsx +46 -0
- package/src/stories/table.stories.tsx +242 -0
- package/src/stories/tabs.stories.tsx +169 -0
- package/src/stories/tag.stories.tsx +82 -0
- package/src/stories/textarea.stories.tsx +60 -0
- package/src/stories/toggle-group.stories.tsx +142 -0
- package/src/stories/tooltip-icon-button.stories.tsx +59 -0
- package/src/stories/tooltip.stories.tsx +54 -0
- package/README.md +0 -33
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
4
|
+
import { Info } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import {
|
|
7
|
+
Controller,
|
|
8
|
+
type ControllerProps,
|
|
9
|
+
type FieldPath,
|
|
10
|
+
type FieldValues,
|
|
11
|
+
FormProvider,
|
|
12
|
+
useFormContext,
|
|
13
|
+
useFormState,
|
|
14
|
+
} from "react-hook-form";
|
|
15
|
+
|
|
16
|
+
import { cn } from "../lib/cn";
|
|
17
|
+
import { Input, type InputProps } from "./input";
|
|
18
|
+
import { Label } from "./label";
|
|
19
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select";
|
|
20
|
+
import { Textarea, type TextareaProps } from "./textarea";
|
|
21
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
22
|
+
|
|
23
|
+
const Form = FormProvider;
|
|
24
|
+
|
|
25
|
+
type FormFieldContextValue<
|
|
26
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
27
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
28
|
+
> = {
|
|
29
|
+
name: TName;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
33
|
+
|
|
34
|
+
const FormField = <
|
|
35
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
36
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
37
|
+
>({
|
|
38
|
+
...props
|
|
39
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
40
|
+
return (
|
|
41
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
42
|
+
<Controller {...props} />
|
|
43
|
+
</FormFieldContext.Provider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const useFormField = () => {
|
|
48
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
49
|
+
const itemContext = React.useContext(FormItemContext);
|
|
50
|
+
|
|
51
|
+
if (!fieldContext.name) {
|
|
52
|
+
throw new Error("useFormField should be used within <FormField>");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { getFieldState } = useFormContext();
|
|
56
|
+
const formState = useFormState({ name: fieldContext.name });
|
|
57
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
58
|
+
|
|
59
|
+
const { id } = itemContext;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
name: fieldContext.name,
|
|
64
|
+
formItemId: `${id}-form-item`,
|
|
65
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
66
|
+
formMessageId: `${id}-form-item-message`,
|
|
67
|
+
...fieldState,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type FormItemContextValue = {
|
|
72
|
+
id: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
76
|
+
|
|
77
|
+
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
78
|
+
const id = React.useId();
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<FormItemContext.Provider value={{ id }}>
|
|
82
|
+
<div data-slot="form-item" className={cn("grid gap-2", className)} {...props} />
|
|
83
|
+
</FormItemContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface FormLabelProps extends React.ComponentProps<typeof Label> {
|
|
88
|
+
required?: boolean;
|
|
89
|
+
tooltip?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function FormLabel({ className, required, tooltip, children, ...props }: FormLabelProps) {
|
|
93
|
+
const { error, formItemId } = useFormField();
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex items-center gap-0.5">
|
|
97
|
+
<Label
|
|
98
|
+
data-slot="form-label"
|
|
99
|
+
data-error={!!error}
|
|
100
|
+
className={cn("data-[error=true]:text-destructive", className)}
|
|
101
|
+
htmlFor={formItemId}
|
|
102
|
+
{...props}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</Label>
|
|
106
|
+
{required && (
|
|
107
|
+
<span aria-hidden className="type-text-sm font-medium text-required">
|
|
108
|
+
*
|
|
109
|
+
</span>
|
|
110
|
+
)}
|
|
111
|
+
{tooltip && (
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger asChild>
|
|
114
|
+
<Info className="size-4 text-quaternary-foreground" />
|
|
115
|
+
</TooltipTrigger>
|
|
116
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
|
124
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Slot
|
|
128
|
+
data-slot="form-control"
|
|
129
|
+
id={formItemId}
|
|
130
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
131
|
+
aria-invalid={!!error}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
138
|
+
const { formDescriptionId } = useFormField();
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<p
|
|
142
|
+
data-slot="form-description"
|
|
143
|
+
id={formDescriptionId}
|
|
144
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
|
151
|
+
const { error, formMessageId } = useFormField();
|
|
152
|
+
const body = error ? String(error?.message ?? "") : props.children;
|
|
153
|
+
|
|
154
|
+
if (!body) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<p data-slot="form-message" id={formMessageId} className={cn("text-destructive text-sm", className)} {...props}>
|
|
160
|
+
{body}
|
|
161
|
+
</p>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Layout components ───────────────────────────────────────────────────── */
|
|
166
|
+
|
|
167
|
+
interface FormHeaderProps extends React.ComponentProps<"div"> {
|
|
168
|
+
title: string;
|
|
169
|
+
description?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function FormHeader({ title, description, className, ...props }: FormHeaderProps) {
|
|
173
|
+
return (
|
|
174
|
+
<div className={cn("flex flex-col gap-1", className)} {...props}>
|
|
175
|
+
<h2 className="type-display-sm font-semibold text-foreground">{title}</h2>
|
|
176
|
+
{description && <p className="type-text-xl text-subtle-foreground">{description}</p>}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function FormFields({ className, ...props }: React.ComponentProps<"div">) {
|
|
182
|
+
return <div className={cn("flex flex-col gap-6", className)} {...props} />;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ── Convenience field wrappers ─────────────────────────────────────────── */
|
|
186
|
+
|
|
187
|
+
interface FormFieldBaseProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>> {
|
|
188
|
+
control: ControllerProps<TFieldValues, TName>["control"];
|
|
189
|
+
name: TName;
|
|
190
|
+
rules?: ControllerProps<TFieldValues, TName>["rules"];
|
|
191
|
+
required?: boolean;
|
|
192
|
+
label?: string;
|
|
193
|
+
description?: string;
|
|
194
|
+
tooltip?: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface InputFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
198
|
+
extends FormFieldBaseProps<TFieldValues, TName>,
|
|
199
|
+
Omit<InputProps, "name" | "label"> {}
|
|
200
|
+
|
|
201
|
+
function InputField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
202
|
+
control,
|
|
203
|
+
name,
|
|
204
|
+
rules,
|
|
205
|
+
required,
|
|
206
|
+
label,
|
|
207
|
+
description,
|
|
208
|
+
tooltip,
|
|
209
|
+
...inputProps
|
|
210
|
+
}: InputFieldProps<TFieldValues, TName>) {
|
|
211
|
+
return (
|
|
212
|
+
<FormField
|
|
213
|
+
control={control}
|
|
214
|
+
name={name}
|
|
215
|
+
rules={rules}
|
|
216
|
+
render={({ field }) => (
|
|
217
|
+
<FormItem>
|
|
218
|
+
{label && (
|
|
219
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
220
|
+
{label}
|
|
221
|
+
</FormLabel>
|
|
222
|
+
)}
|
|
223
|
+
<FormControl>
|
|
224
|
+
<Input {...inputProps} {...field} />
|
|
225
|
+
</FormControl>
|
|
226
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
227
|
+
<FormMessage />
|
|
228
|
+
</FormItem>
|
|
229
|
+
)}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface TextareaFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
235
|
+
extends FormFieldBaseProps<TFieldValues, TName>,
|
|
236
|
+
Omit<TextareaProps, "name" | "label"> {}
|
|
237
|
+
|
|
238
|
+
function TextareaField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
239
|
+
control,
|
|
240
|
+
name,
|
|
241
|
+
rules,
|
|
242
|
+
required,
|
|
243
|
+
label,
|
|
244
|
+
description,
|
|
245
|
+
tooltip,
|
|
246
|
+
...textareaProps
|
|
247
|
+
}: TextareaFieldProps<TFieldValues, TName>) {
|
|
248
|
+
return (
|
|
249
|
+
<FormField
|
|
250
|
+
control={control}
|
|
251
|
+
name={name}
|
|
252
|
+
rules={rules}
|
|
253
|
+
render={({ field }) => (
|
|
254
|
+
<FormItem>
|
|
255
|
+
{label && (
|
|
256
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
257
|
+
{label}
|
|
258
|
+
</FormLabel>
|
|
259
|
+
)}
|
|
260
|
+
<FormControl>
|
|
261
|
+
<Textarea {...textareaProps} {...field} />
|
|
262
|
+
</FormControl>
|
|
263
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
264
|
+
<FormMessage />
|
|
265
|
+
</FormItem>
|
|
266
|
+
)}
|
|
267
|
+
/>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
interface SelectFieldOption {
|
|
272
|
+
value: string;
|
|
273
|
+
label: string;
|
|
274
|
+
disabled?: boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface SelectFieldProps<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>
|
|
278
|
+
extends FormFieldBaseProps<TFieldValues, TName> {
|
|
279
|
+
options: SelectFieldOption[];
|
|
280
|
+
placeholder?: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function SelectField<TFieldValues extends FieldValues, TName extends FieldPath<TFieldValues>>({
|
|
284
|
+
control,
|
|
285
|
+
name,
|
|
286
|
+
rules,
|
|
287
|
+
required,
|
|
288
|
+
label,
|
|
289
|
+
description,
|
|
290
|
+
tooltip,
|
|
291
|
+
options,
|
|
292
|
+
placeholder,
|
|
293
|
+
}: SelectFieldProps<TFieldValues, TName>) {
|
|
294
|
+
return (
|
|
295
|
+
<FormField
|
|
296
|
+
control={control}
|
|
297
|
+
name={name}
|
|
298
|
+
rules={rules}
|
|
299
|
+
render={({ field }) => (
|
|
300
|
+
<FormItem>
|
|
301
|
+
{label && (
|
|
302
|
+
<FormLabel required={required} tooltip={tooltip}>
|
|
303
|
+
{label}
|
|
304
|
+
</FormLabel>
|
|
305
|
+
)}
|
|
306
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
307
|
+
<FormControl>
|
|
308
|
+
<SelectTrigger>
|
|
309
|
+
<SelectValue placeholder={placeholder} />
|
|
310
|
+
</SelectTrigger>
|
|
311
|
+
</FormControl>
|
|
312
|
+
<SelectContent>
|
|
313
|
+
{options.map((option) => (
|
|
314
|
+
<SelectItem key={option.value} value={option.value} disabled={option.disabled}>
|
|
315
|
+
{option.label}
|
|
316
|
+
</SelectItem>
|
|
317
|
+
))}
|
|
318
|
+
</SelectContent>
|
|
319
|
+
</Select>
|
|
320
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
321
|
+
<FormMessage />
|
|
322
|
+
</FormItem>
|
|
323
|
+
)}
|
|
324
|
+
/>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type { SelectFieldOption };
|
|
329
|
+
export {
|
|
330
|
+
Form,
|
|
331
|
+
FormControl,
|
|
332
|
+
FormDescription,
|
|
333
|
+
FormField,
|
|
334
|
+
FormFields,
|
|
335
|
+
FormHeader,
|
|
336
|
+
FormItem,
|
|
337
|
+
FormLabel,
|
|
338
|
+
FormMessage,
|
|
339
|
+
InputField,
|
|
340
|
+
SelectField,
|
|
341
|
+
TextareaField,
|
|
342
|
+
useFormField,
|
|
343
|
+
};
|
package/src/components/input.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Info } from "lucide-react";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
|
|
5
6
|
import { cn } from "../lib/cn";
|
|
6
7
|
import { Label } from "./label";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
7
9
|
|
|
8
10
|
const inputSizeStyles = {
|
|
9
11
|
sm: {
|
|
@@ -29,6 +31,7 @@ interface InputProps extends Omit<React.ComponentProps<"input">, "size"> {
|
|
|
29
31
|
required?: boolean;
|
|
30
32
|
hint?: string;
|
|
31
33
|
error?: string;
|
|
34
|
+
tooltip?: string;
|
|
32
35
|
leadingText?: string;
|
|
33
36
|
leadingIcon?: React.ReactNode;
|
|
34
37
|
size?: "sm" | "md";
|
|
@@ -41,6 +44,7 @@ function Input({
|
|
|
41
44
|
required,
|
|
42
45
|
hint,
|
|
43
46
|
error,
|
|
47
|
+
tooltip,
|
|
44
48
|
leadingText,
|
|
45
49
|
leadingIcon,
|
|
46
50
|
size = "md",
|
|
@@ -126,6 +130,14 @@ function Input({
|
|
|
126
130
|
*
|
|
127
131
|
</span>
|
|
128
132
|
)}
|
|
133
|
+
{tooltip && (
|
|
134
|
+
<Tooltip>
|
|
135
|
+
<TooltipTrigger asChild>
|
|
136
|
+
<Info className="size-4 text-muted-foreground" />
|
|
137
|
+
</TooltipTrigger>
|
|
138
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
139
|
+
</Tooltip>
|
|
140
|
+
)}
|
|
129
141
|
</div>
|
|
130
142
|
)}
|
|
131
143
|
{wrappedInput}
|
|
@@ -12,6 +12,7 @@ export const selectTriggerVariants = cva(
|
|
|
12
12
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
13
13
|
"data-[state=open]:ring-2 data-[state=open]:ring-ring data-[state=open]:ring-offset-2 data-[state=open]:ring-offset-background",
|
|
14
14
|
"disabled:pointer-events-none disabled:bg-disabled disabled:text-disabled-foreground disabled:border-disabled",
|
|
15
|
+
"aria-invalid:border-border-error",
|
|
15
16
|
"[&_svg:not([class*='size-'])]:size-5 [&_svg]:shrink-0",
|
|
16
17
|
].join(" "),
|
|
17
18
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
4
|
import type { VariantProps } from "class-variance-authority";
|
|
5
|
-
import { CheckIcon, ChevronDownIcon
|
|
5
|
+
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
6
6
|
import type * as React from "react";
|
|
7
7
|
|
|
8
8
|
import { cn } from "../lib/cn";
|
|
@@ -75,7 +75,6 @@ function SelectContent({
|
|
|
75
75
|
position={position}
|
|
76
76
|
{...props}
|
|
77
77
|
>
|
|
78
|
-
<SelectScrollUpButton />
|
|
79
78
|
<SelectPrimitive.Viewport
|
|
80
79
|
className={cn(
|
|
81
80
|
"p-1",
|
|
@@ -85,7 +84,6 @@ function SelectContent({
|
|
|
85
84
|
>
|
|
86
85
|
{children}
|
|
87
86
|
</SelectPrimitive.Viewport>
|
|
88
|
-
<SelectScrollDownButton />
|
|
89
87
|
</SelectPrimitive.Content>
|
|
90
88
|
</SelectPrimitive.Portal>
|
|
91
89
|
);
|
|
@@ -110,7 +108,7 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
|
|
|
110
108
|
<SelectPrimitive.Item
|
|
111
109
|
data-slot="select-item"
|
|
112
110
|
className={cn(
|
|
113
|
-
"relative flex
|
|
111
|
+
"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pl-2 outline-hidden select-none",
|
|
114
112
|
"type-text-md font-medium text-foreground mx-1 my-px",
|
|
115
113
|
"[@media(hover:hover)]:hover:bg-background-hover",
|
|
116
114
|
"focus:bg-background-hover",
|
|
@@ -142,43 +140,12 @@ function SelectSeparator({ className, ...props }: React.ComponentProps<typeof Se
|
|
|
142
140
|
);
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
/* ── Scroll buttons ──────────────────────────────────────────────────────── */
|
|
146
|
-
|
|
147
|
-
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
148
|
-
return (
|
|
149
|
-
<SelectPrimitive.ScrollUpButton
|
|
150
|
-
data-slot="select-scroll-up-button"
|
|
151
|
-
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
152
|
-
{...props}
|
|
153
|
-
>
|
|
154
|
-
<ChevronUpIcon className="size-4" />
|
|
155
|
-
</SelectPrimitive.ScrollUpButton>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function SelectScrollDownButton({
|
|
160
|
-
className,
|
|
161
|
-
...props
|
|
162
|
-
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
163
|
-
return (
|
|
164
|
-
<SelectPrimitive.ScrollDownButton
|
|
165
|
-
data-slot="select-scroll-down-button"
|
|
166
|
-
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
|
167
|
-
{...props}
|
|
168
|
-
>
|
|
169
|
-
<ChevronDownIcon className="size-4" />
|
|
170
|
-
</SelectPrimitive.ScrollDownButton>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
143
|
export {
|
|
175
144
|
Select,
|
|
176
145
|
SelectContent,
|
|
177
146
|
SelectGroup,
|
|
178
147
|
SelectItem,
|
|
179
148
|
SelectLabel,
|
|
180
|
-
SelectScrollDownButton,
|
|
181
|
-
SelectScrollUpButton,
|
|
182
149
|
SelectSeparator,
|
|
183
150
|
SelectTrigger,
|
|
184
151
|
SelectValue,
|
|
@@ -316,21 +316,19 @@ function SidebarHeader({ className, icon, title, children, ...props }: SidebarHe
|
|
|
316
316
|
<div
|
|
317
317
|
data-slot="sidebar-header"
|
|
318
318
|
data-sidebar="header"
|
|
319
|
-
className={cn("flex flex-col gap-2 p-2
|
|
319
|
+
className={cn("flex flex-col gap-2 p-2", className)}
|
|
320
320
|
{...props}
|
|
321
321
|
>
|
|
322
|
-
<div className="flex h-8 items-center gap-2 px-
|
|
322
|
+
<div className="flex h-8 items-center gap-2 px-3">
|
|
323
323
|
<div className="relative shrink-0">
|
|
324
324
|
<span className="transition-opacity group-data-[collapsible=icon]:group-hover:opacity-0">{icon}</span>
|
|
325
325
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-data-[collapsible=icon]:group-hover:opacity-100">
|
|
326
|
-
<SidebarTrigger
|
|
326
|
+
<SidebarTrigger />
|
|
327
327
|
</div>
|
|
328
328
|
</div>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
</span>
|
|
333
|
-
)}
|
|
329
|
+
<span className="text-foreground text-md min-w-0 truncate font-medium group-data-[collapsible=icon]:hidden">
|
|
330
|
+
{title}
|
|
331
|
+
</span>
|
|
334
332
|
<SidebarTrigger className="ml-auto shrink-0 group-data-[collapsible=icon]:hidden" />
|
|
335
333
|
</div>
|
|
336
334
|
{children}
|
|
@@ -376,7 +374,7 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
376
374
|
<div
|
|
377
375
|
data-slot="sidebar-group"
|
|
378
376
|
data-sidebar="group"
|
|
379
|
-
className={cn("relative flex w-full min-w-0 flex-col
|
|
377
|
+
className={cn("relative flex w-full min-w-0 flex-col", className)}
|
|
380
378
|
{...props}
|
|
381
379
|
/>
|
|
382
380
|
);
|
|
@@ -453,7 +451,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
|
453
451
|
<li
|
|
454
452
|
data-slot="sidebar-menu-item"
|
|
455
453
|
data-sidebar="menu-item"
|
|
456
|
-
className={cn("group/menu-item relative", className)}
|
|
454
|
+
className={cn("group/menu-item relative px-3", className)}
|
|
457
455
|
{...props}
|
|
458
456
|
/>
|
|
459
457
|
);
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Info } from "lucide-react";
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
|
|
5
6
|
import { cn } from "../lib/cn";
|
|
6
7
|
import { Label } from "./label";
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
7
9
|
|
|
8
10
|
interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
9
11
|
label?: string;
|
|
10
12
|
required?: boolean;
|
|
11
13
|
hint?: string;
|
|
12
14
|
error?: string;
|
|
15
|
+
tooltip?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
function Textarea({ className, id, label, required, hint, error, ...props }: TextareaProps) {
|
|
18
|
+
function Textarea({ className, id, label, required, hint, error, tooltip, ...props }: TextareaProps) {
|
|
16
19
|
const generatedId = React.useId();
|
|
17
20
|
const fieldId = id ?? generatedId;
|
|
18
21
|
|
|
@@ -51,6 +54,14 @@ function Textarea({ className, id, label, required, hint, error, ...props }: Tex
|
|
|
51
54
|
*
|
|
52
55
|
</span>
|
|
53
56
|
)}
|
|
57
|
+
{tooltip && (
|
|
58
|
+
<Tooltip>
|
|
59
|
+
<TooltipTrigger asChild>
|
|
60
|
+
<Info className="size-4 text-muted-foreground" />
|
|
61
|
+
</TooltipTrigger>
|
|
62
|
+
<TooltipContent>{tooltip}</TooltipContent>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
)}
|
|
54
65
|
</div>
|
|
55
66
|
)}
|
|
56
67
|
{textarea}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
import {
|
|
3
|
+
AccordionCard,
|
|
4
|
+
AccordionCardContent,
|
|
5
|
+
AccordionCardHeader,
|
|
6
|
+
AccordionCardTitle,
|
|
7
|
+
} from "../components/accordion-card";
|
|
8
|
+
|
|
9
|
+
export const AllVariants: Story = () => (
|
|
10
|
+
<div className="flex flex-col gap-6 max-w-md">
|
|
11
|
+
<div className="flex flex-col gap-1.5">
|
|
12
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Default (collapsed)</p>
|
|
13
|
+
<AccordionCard>
|
|
14
|
+
<AccordionCardHeader>
|
|
15
|
+
<AccordionCardTitle>Logs</AccordionCardTitle>
|
|
16
|
+
</AccordionCardHeader>
|
|
17
|
+
<AccordionCardContent>Content inside the accordion card.</AccordionCardContent>
|
|
18
|
+
</AccordionCard>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div className="flex flex-col gap-1.5">
|
|
22
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Default open</p>
|
|
23
|
+
<AccordionCard defaultOpen>
|
|
24
|
+
<AccordionCardHeader>
|
|
25
|
+
<AccordionCardTitle>Build settings</AccordionCardTitle>
|
|
26
|
+
</AccordionCardHeader>
|
|
27
|
+
<AccordionCardContent>
|
|
28
|
+
This card starts open. The chevron rotates to indicate the open state.
|
|
29
|
+
</AccordionCardContent>
|
|
30
|
+
</AccordionCard>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="flex flex-col gap-1.5">
|
|
34
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Without title component</p>
|
|
35
|
+
<AccordionCard>
|
|
36
|
+
<AccordionCardHeader>
|
|
37
|
+
<span className="type-text-sm font-medium">Environment Variables</span>
|
|
38
|
+
</AccordionCardHeader>
|
|
39
|
+
<AccordionCardContent>Header text can be any element, not just AccordionCardTitle.</AccordionCardContent>
|
|
40
|
+
</AccordionCard>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="flex flex-col gap-1.5">
|
|
44
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Disabled</p>
|
|
45
|
+
<AccordionCard disabled>
|
|
46
|
+
<AccordionCardHeader>
|
|
47
|
+
<AccordionCardTitle>Disabled card</AccordionCardTitle>
|
|
48
|
+
</AccordionCardHeader>
|
|
49
|
+
<AccordionCardContent>Cannot be opened.</AccordionCardContent>
|
|
50
|
+
</AccordionCard>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../components/accordion";
|
|
3
|
+
|
|
4
|
+
export const AllVariants: Story = () => (
|
|
5
|
+
<div className="flex flex-col gap-10 max-w-md">
|
|
6
|
+
<div className="flex flex-col gap-1.5">
|
|
7
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Single (collapsible)</p>
|
|
8
|
+
<Accordion type="single" collapsible>
|
|
9
|
+
<AccordionItem value="item-1">
|
|
10
|
+
<AccordionTrigger>What is Alpic?</AccordionTrigger>
|
|
11
|
+
<AccordionContent>
|
|
12
|
+
Alpic is a multi-tenant SaaS platform for deploying MCP servers. It handles infrastructure, scaling, and
|
|
13
|
+
distribution so you can focus on building.
|
|
14
|
+
</AccordionContent>
|
|
15
|
+
</AccordionItem>
|
|
16
|
+
<AccordionItem value="item-2">
|
|
17
|
+
<AccordionTrigger>How do I deploy?</AccordionTrigger>
|
|
18
|
+
<AccordionContent>
|
|
19
|
+
Use the Alpic CLI to deploy your MCP server with a single command. The platform handles containerization,
|
|
20
|
+
routing, and scaling automatically.
|
|
21
|
+
</AccordionContent>
|
|
22
|
+
</AccordionItem>
|
|
23
|
+
<AccordionItem value="item-3">
|
|
24
|
+
<AccordionTrigger>What languages are supported?</AccordionTrigger>
|
|
25
|
+
<AccordionContent>
|
|
26
|
+
Any language that can run in a container. TypeScript, Python, Go, Rust, and more are all supported out of
|
|
27
|
+
the box with pre-built runtime images.
|
|
28
|
+
</AccordionContent>
|
|
29
|
+
</AccordionItem>
|
|
30
|
+
</Accordion>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="flex flex-col gap-1.5">
|
|
34
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Multiple</p>
|
|
35
|
+
<Accordion type="multiple" defaultValue={["item-1", "item-3"]}>
|
|
36
|
+
<AccordionItem value="item-1">
|
|
37
|
+
<AccordionTrigger>First item (open by default)</AccordionTrigger>
|
|
38
|
+
<AccordionContent>Content for the first item. Multiple items can be open at the same time.</AccordionContent>
|
|
39
|
+
</AccordionItem>
|
|
40
|
+
<AccordionItem value="item-2">
|
|
41
|
+
<AccordionTrigger>Second item</AccordionTrigger>
|
|
42
|
+
<AccordionContent>Content for the second item.</AccordionContent>
|
|
43
|
+
</AccordionItem>
|
|
44
|
+
<AccordionItem value="item-3">
|
|
45
|
+
<AccordionTrigger>Third item (open by default)</AccordionTrigger>
|
|
46
|
+
<AccordionContent>Content for the third item.</AccordionContent>
|
|
47
|
+
</AccordionItem>
|
|
48
|
+
</Accordion>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div className="flex flex-col gap-1.5">
|
|
52
|
+
<p className="type-text-xs font-medium text-subtle-foreground">Disabled</p>
|
|
53
|
+
<Accordion type="single" collapsible>
|
|
54
|
+
<AccordionItem value="item-1">
|
|
55
|
+
<AccordionTrigger>Enabled item</AccordionTrigger>
|
|
56
|
+
<AccordionContent>This item can be toggled.</AccordionContent>
|
|
57
|
+
</AccordionItem>
|
|
58
|
+
<AccordionItem value="item-2" disabled>
|
|
59
|
+
<AccordionTrigger>Disabled item</AccordionTrigger>
|
|
60
|
+
<AccordionContent>This content cannot be reached.</AccordionContent>
|
|
61
|
+
</AccordionItem>
|
|
62
|
+
</Accordion>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|