@ttt-productions/ui-core 0.11.18 → 0.11.19
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 +2 -4
- package/src/index.ts +0 -3
- package/src/lib/format-numbers.ts +0 -17
- package/src/lib/utils.ts +0 -9
- package/src/react/components/accordion.tsx +0 -53
- package/src/react/components/alert-dialog.tsx +0 -131
- package/src/react/components/alert.tsx +0 -59
- package/src/react/components/avatar.tsx +0 -51
- package/src/react/components/badge.tsx +0 -31
- package/src/react/components/button.tsx +0 -102
- package/src/react/components/card.tsx +0 -54
- package/src/react/components/checkbox.tsx +0 -28
- package/src/react/components/chunk-error-recovery.tsx +0 -57
- package/src/react/components/collapsible.tsx +0 -11
- package/src/react/components/consequence-dialog.tsx +0 -282
- package/src/react/components/date-picker.tsx +0 -567
- package/src/react/components/dialog.tsx +0 -114
- package/src/react/components/dropdown-menu.tsx +0 -200
- package/src/react/components/end-of-list-indicator.tsx +0 -27
- package/src/react/components/form.tsx +0 -174
- package/src/react/components/input.tsx +0 -23
- package/src/react/components/label.tsx +0 -21
- package/src/react/components/layout/screen-adaptive-view.tsx +0 -106
- package/src/react/components/list-pagination.tsx +0 -101
- package/src/react/components/menubar.tsx +0 -220
- package/src/react/components/popover.tsx +0 -31
- package/src/react/components/progress.tsx +0 -27
- package/src/react/components/radio-group.tsx +0 -36
- package/src/react/components/relative-time.tsx +0 -56
- package/src/react/components/scroll-area.tsx +0 -56
- package/src/react/components/scroll-to-top-button.tsx +0 -85
- package/src/react/components/search-dropdown.tsx +0 -233
- package/src/react/components/select.tsx +0 -165
- package/src/react/components/separator.tsx +0 -26
- package/src/react/components/sheet.tsx +0 -140
- package/src/react/components/skeleton.tsx +0 -16
- package/src/react/components/slider.tsx +0 -25
- package/src/react/components/spinner.tsx +0 -49
- package/src/react/components/switch.tsx +0 -41
- package/src/react/components/table.tsx +0 -121
- package/src/react/components/tabs.tsx +0 -59
- package/src/react/components/textarea.tsx +0 -22
- package/src/react/components/toast.tsx +0 -193
- package/src/react/components/toaster.tsx +0 -43
- package/src/react/components/tooltip.tsx +0 -28
- package/src/react/hooks/use-async-action.ts +0 -63
- package/src/react/hooks/use-cursor-page.ts +0 -139
- package/src/react/hooks/use-media-query.tsx +0 -26
- package/src/react/hooks/use-paged-list.ts +0 -106
- package/src/react/hooks/use-return-scroll.ts +0 -149
- package/src/react/hooks/use-toast.tsx +0 -176
- package/src/react/index.ts +0 -85
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
5
|
-
import { Slottable } from "@radix-ui/react-slot"
|
|
6
|
-
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
7
|
-
|
|
8
|
-
import { cn } from "../../lib/utils.js"
|
|
9
|
-
import { Spinner } from "./spinner.js"
|
|
10
|
-
|
|
11
|
-
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
12
|
-
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
13
|
-
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
14
|
-
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
15
|
-
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
16
|
-
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
17
|
-
|
|
18
|
-
const DropdownMenuSubTrigger = React.forwardRef<
|
|
19
|
-
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
20
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
21
|
-
inset?: boolean
|
|
22
|
-
}
|
|
23
|
-
>(({ className, inset, children, ...props }, ref) => (
|
|
24
|
-
<DropdownMenuPrimitive.SubTrigger
|
|
25
|
-
ref={ref}
|
|
26
|
-
className={cn(
|
|
27
|
-
"flex cursor-default gap-2 select-none items-center rounded-xs px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
28
|
-
inset && "pl-8",
|
|
29
|
-
className
|
|
30
|
-
)}
|
|
31
|
-
{...props}
|
|
32
|
-
>
|
|
33
|
-
{children}
|
|
34
|
-
<ChevronRight className="ml-auto" />
|
|
35
|
-
</DropdownMenuPrimitive.SubTrigger>
|
|
36
|
-
))
|
|
37
|
-
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
|
38
|
-
|
|
39
|
-
const DropdownMenuSubContent = React.forwardRef<
|
|
40
|
-
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
41
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
42
|
-
>(({ className, ...props }, ref) => (
|
|
43
|
-
<DropdownMenuPrimitive.SubContent
|
|
44
|
-
ref={ref}
|
|
45
|
-
className={cn(
|
|
46
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground elevation-raised data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
47
|
-
className
|
|
48
|
-
)}
|
|
49
|
-
{...props}
|
|
50
|
-
/>
|
|
51
|
-
))
|
|
52
|
-
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
|
|
53
|
-
|
|
54
|
-
const DropdownMenuContent = React.forwardRef<
|
|
55
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
56
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
57
|
-
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
58
|
-
<DropdownMenuPrimitive.Portal>
|
|
59
|
-
<DropdownMenuPrimitive.Content
|
|
60
|
-
ref={ref}
|
|
61
|
-
sideOffset={sideOffset}
|
|
62
|
-
className={cn(
|
|
63
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-md border !bg-card p-1 text-popover-foreground elevation-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
64
|
-
className
|
|
65
|
-
)}
|
|
66
|
-
{...props}
|
|
67
|
-
/>
|
|
68
|
-
</DropdownMenuPrimitive.Portal>
|
|
69
|
-
))
|
|
70
|
-
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
71
|
-
|
|
72
|
-
const DropdownMenuItem = React.forwardRef<
|
|
73
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
74
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
75
|
-
inset?: boolean
|
|
76
|
-
/**
|
|
77
|
-
* The action this item started is in flight: the item disables itself, sets
|
|
78
|
-
* `aria-busy`, and shows the spinner in place of its `icon`. Keeping the menu open
|
|
79
|
-
* meanwhile is the caller's `onSelect` (`event.preventDefault()`, close on settle).
|
|
80
|
-
*/
|
|
81
|
-
pending?: boolean
|
|
82
|
-
/** Leading icon. While `pending`, the spinner takes its place. */
|
|
83
|
-
icon?: React.ReactNode
|
|
84
|
-
}
|
|
85
|
-
>(({ className, inset, pending = false, icon, disabled, children, ...props }, ref) => (
|
|
86
|
-
<DropdownMenuPrimitive.Item
|
|
87
|
-
ref={ref}
|
|
88
|
-
className={cn(
|
|
89
|
-
"relative flex cursor-default select-none items-center gap-2 rounded-xs px-2 py-1.5 text-sm outline-hidden transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 app-dropdown-item",
|
|
90
|
-
inset && "pl-8",
|
|
91
|
-
className
|
|
92
|
-
)}
|
|
93
|
-
{...props}
|
|
94
|
-
{...(pending ? { "aria-busy": true, "data-pending": "" } : {})}
|
|
95
|
-
disabled={disabled || pending || undefined}
|
|
96
|
-
>
|
|
97
|
-
{/* Direct siblings, never a fragment: under `asChild` Radix Slot finds the Slottable
|
|
98
|
-
among its direct children and renders the icon/spinner inside the child element. */}
|
|
99
|
-
{pending ? <Spinner size="xs" /> : icon}
|
|
100
|
-
<Slottable>{children}</Slottable>
|
|
101
|
-
</DropdownMenuPrimitive.Item>
|
|
102
|
-
))
|
|
103
|
-
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
104
|
-
|
|
105
|
-
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
106
|
-
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
107
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
108
|
-
>(({ className, children, checked, ...props }, ref) => (
|
|
109
|
-
<DropdownMenuPrimitive.CheckboxItem
|
|
110
|
-
ref={ref}
|
|
111
|
-
className={cn(
|
|
112
|
-
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
113
|
-
className
|
|
114
|
-
)}
|
|
115
|
-
checked={checked}
|
|
116
|
-
{...props}
|
|
117
|
-
>
|
|
118
|
-
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
119
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
120
|
-
<Check className="h-4 w-4" />
|
|
121
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
122
|
-
</span>
|
|
123
|
-
{children}
|
|
124
|
-
</DropdownMenuPrimitive.CheckboxItem>
|
|
125
|
-
))
|
|
126
|
-
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
|
|
127
|
-
|
|
128
|
-
const DropdownMenuRadioItem = React.forwardRef<
|
|
129
|
-
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
130
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
131
|
-
>(({ className, children, ...props }, ref) => (
|
|
132
|
-
<DropdownMenuPrimitive.RadioItem
|
|
133
|
-
ref={ref}
|
|
134
|
-
className={cn(
|
|
135
|
-
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
136
|
-
className
|
|
137
|
-
)}
|
|
138
|
-
{...props}
|
|
139
|
-
>
|
|
140
|
-
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
141
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
142
|
-
<Circle className="h-2 w-2 fill-current" />
|
|
143
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
144
|
-
</span>
|
|
145
|
-
{children}
|
|
146
|
-
</DropdownMenuPrimitive.RadioItem>
|
|
147
|
-
))
|
|
148
|
-
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
149
|
-
|
|
150
|
-
const DropdownMenuLabel = React.forwardRef<
|
|
151
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
152
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
153
|
-
inset?: boolean
|
|
154
|
-
}
|
|
155
|
-
>(({ className, inset, ...props }, ref) => (
|
|
156
|
-
<DropdownMenuPrimitive.Label
|
|
157
|
-
ref={ref}
|
|
158
|
-
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
|
159
|
-
{...props}
|
|
160
|
-
/>
|
|
161
|
-
))
|
|
162
|
-
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
163
|
-
|
|
164
|
-
const DropdownMenuSeparator = React.forwardRef<
|
|
165
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
166
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
167
|
-
>(({ className, ...props }, ref) => (
|
|
168
|
-
<DropdownMenuPrimitive.Separator
|
|
169
|
-
ref={ref}
|
|
170
|
-
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
171
|
-
{...props}
|
|
172
|
-
/>
|
|
173
|
-
))
|
|
174
|
-
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
175
|
-
|
|
176
|
-
const DropdownMenuShortcut = ({
|
|
177
|
-
className,
|
|
178
|
-
...props
|
|
179
|
-
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
180
|
-
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
|
181
|
-
)
|
|
182
|
-
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
|
183
|
-
|
|
184
|
-
export {
|
|
185
|
-
DropdownMenu,
|
|
186
|
-
DropdownMenuTrigger,
|
|
187
|
-
DropdownMenuContent,
|
|
188
|
-
DropdownMenuItem,
|
|
189
|
-
DropdownMenuCheckboxItem,
|
|
190
|
-
DropdownMenuRadioItem,
|
|
191
|
-
DropdownMenuLabel,
|
|
192
|
-
DropdownMenuSeparator,
|
|
193
|
-
DropdownMenuShortcut,
|
|
194
|
-
DropdownMenuGroup,
|
|
195
|
-
DropdownMenuPortal,
|
|
196
|
-
DropdownMenuSub,
|
|
197
|
-
DropdownMenuSubContent,
|
|
198
|
-
DropdownMenuSubTrigger,
|
|
199
|
-
DropdownMenuRadioGroup,
|
|
200
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { CheckCircle2 } from "lucide-react";
|
|
3
|
-
import { cn } from "../../lib/utils.js";
|
|
4
|
-
|
|
5
|
-
export interface EndOfListIndicatorProps {
|
|
6
|
-
message?: string;
|
|
7
|
-
className?: string;
|
|
8
|
-
icon?: React.ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function EndOfListIndicator({
|
|
12
|
-
message = "You've reached the end!",
|
|
13
|
-
className,
|
|
14
|
-
icon,
|
|
15
|
-
}: EndOfListIndicatorProps) {
|
|
16
|
-
return (
|
|
17
|
-
<div
|
|
18
|
-
className={cn(
|
|
19
|
-
"flex flex-col items-center gap-2 py-8 text-center text-muted-foreground",
|
|
20
|
-
className,
|
|
21
|
-
)}
|
|
22
|
-
>
|
|
23
|
-
{icon ?? <CheckCircle2 className="h-6 w-6 text-[color:var(--success-icon)]" />}
|
|
24
|
-
<p className="text-sm">{message}</p>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
5
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
6
|
-
import {
|
|
7
|
-
Controller,
|
|
8
|
-
FormProvider,
|
|
9
|
-
useFormContext,
|
|
10
|
-
type ControllerProps,
|
|
11
|
-
type FieldPath,
|
|
12
|
-
type FieldValues,
|
|
13
|
-
} from "react-hook-form"
|
|
14
|
-
|
|
15
|
-
import { cn } from "../../lib/utils.js"
|
|
16
|
-
import { Label } from "./label.js"
|
|
17
|
-
|
|
18
|
-
const Form = FormProvider
|
|
19
|
-
|
|
20
|
-
type FormFieldContextValue<
|
|
21
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
22
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
|
23
|
-
> = {
|
|
24
|
-
name: TName
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
28
|
-
{} as FormFieldContextValue
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const FormField = <
|
|
32
|
-
TFieldValues extends FieldValues = FieldValues,
|
|
33
|
-
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
|
34
|
-
>({
|
|
35
|
-
...props
|
|
36
|
-
}: ControllerProps<TFieldValues, TName>) => {
|
|
37
|
-
return (
|
|
38
|
-
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
39
|
-
<Controller {...props} />
|
|
40
|
-
</FormFieldContext.Provider>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const useFormField = () => {
|
|
45
|
-
const fieldContext = React.useContext(FormFieldContext)
|
|
46
|
-
const itemContext = React.useContext(FormItemContext)
|
|
47
|
-
const { getFieldState, formState } = useFormContext()
|
|
48
|
-
|
|
49
|
-
const fieldState = getFieldState(fieldContext.name, formState)
|
|
50
|
-
|
|
51
|
-
if (!fieldContext) {
|
|
52
|
-
throw new Error("useFormField should be used within <FormField>")
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const { id } = itemContext
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
id,
|
|
59
|
-
name: fieldContext.name,
|
|
60
|
-
formItemId: `${id}-form-item`,
|
|
61
|
-
formDescriptionId: `${id}-form-item-description`,
|
|
62
|
-
formMessageId: `${id}-form-item-message`,
|
|
63
|
-
...fieldState,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type FormItemContextValue = {
|
|
68
|
-
id: string
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
72
|
-
{} as FormItemContextValue
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
const FormItem = React.forwardRef<
|
|
76
|
-
HTMLDivElement,
|
|
77
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
78
|
-
>(({ className, ...props }, ref) => {
|
|
79
|
-
const id = React.useId()
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<FormItemContext.Provider value={{ id }}>
|
|
83
|
-
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
|
84
|
-
</FormItemContext.Provider>
|
|
85
|
-
)
|
|
86
|
-
})
|
|
87
|
-
FormItem.displayName = "FormItem"
|
|
88
|
-
|
|
89
|
-
const FormLabel = React.forwardRef<
|
|
90
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
91
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
92
|
-
>(({ className, ...props }, ref) => {
|
|
93
|
-
const { error, formItemId } = useFormField()
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<Label
|
|
97
|
-
ref={ref}
|
|
98
|
-
className={cn(error && "text-destructive", className)}
|
|
99
|
-
htmlFor={formItemId}
|
|
100
|
-
{...props}
|
|
101
|
-
/>
|
|
102
|
-
)
|
|
103
|
-
})
|
|
104
|
-
FormLabel.displayName = "FormLabel"
|
|
105
|
-
|
|
106
|
-
const FormControl = React.forwardRef<
|
|
107
|
-
React.ElementRef<typeof Slot>,
|
|
108
|
-
React.ComponentPropsWithoutRef<typeof Slot>
|
|
109
|
-
>(({ ...props }, ref) => {
|
|
110
|
-
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<Slot
|
|
114
|
-
ref={ref}
|
|
115
|
-
id={formItemId}
|
|
116
|
-
aria-describedby={
|
|
117
|
-
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
|
118
|
-
}
|
|
119
|
-
aria-invalid={!!error}
|
|
120
|
-
{...props}
|
|
121
|
-
/>
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
FormControl.displayName = "FormControl"
|
|
125
|
-
|
|
126
|
-
const FormDescription = React.forwardRef<
|
|
127
|
-
HTMLParagraphElement,
|
|
128
|
-
React.HTMLAttributes<HTMLParagraphElement>
|
|
129
|
-
>(({ className, ...props }, ref) => {
|
|
130
|
-
const { formDescriptionId } = useFormField()
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<p
|
|
134
|
-
ref={ref}
|
|
135
|
-
id={formDescriptionId}
|
|
136
|
-
className={cn("text-sm text-muted-foreground break-words", className)}
|
|
137
|
-
{...props}
|
|
138
|
-
/>
|
|
139
|
-
)
|
|
140
|
-
})
|
|
141
|
-
FormDescription.displayName = "FormDescription"
|
|
142
|
-
|
|
143
|
-
const FormMessage = React.forwardRef<
|
|
144
|
-
HTMLParagraphElement,
|
|
145
|
-
React.HTMLAttributes<HTMLParagraphElement>
|
|
146
|
-
>(({ className, children, ...props }, ref) => {
|
|
147
|
-
const { error, formMessageId } = useFormField()
|
|
148
|
-
const body = error ? String(error?.message ?? "") : children
|
|
149
|
-
|
|
150
|
-
if (!body) return null
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<p
|
|
154
|
-
ref={ref}
|
|
155
|
-
id={formMessageId}
|
|
156
|
-
className={cn("text-sm font-medium text-destructive break-words", className)}
|
|
157
|
-
{...props}
|
|
158
|
-
>
|
|
159
|
-
{body}
|
|
160
|
-
</p>
|
|
161
|
-
)
|
|
162
|
-
})
|
|
163
|
-
FormMessage.displayName = "FormMessage"
|
|
164
|
-
|
|
165
|
-
export {
|
|
166
|
-
useFormField,
|
|
167
|
-
Form,
|
|
168
|
-
FormItem,
|
|
169
|
-
FormLabel,
|
|
170
|
-
FormControl,
|
|
171
|
-
FormDescription,
|
|
172
|
-
FormMessage,
|
|
173
|
-
FormField,
|
|
174
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { cn } from "../../lib/utils.js"
|
|
5
|
-
|
|
6
|
-
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
7
|
-
({ className, type, ...props }, ref) => {
|
|
8
|
-
return (
|
|
9
|
-
<input
|
|
10
|
-
type={type}
|
|
11
|
-
className={cn(
|
|
12
|
-
"flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
13
|
-
className
|
|
14
|
-
)}
|
|
15
|
-
ref={ref}
|
|
16
|
-
{...props}
|
|
17
|
-
/>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
)
|
|
21
|
-
Input.displayName = "Input"
|
|
22
|
-
|
|
23
|
-
export { Input }
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
5
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
-
|
|
7
|
-
import { cn } from "../../lib/utils.js"
|
|
8
|
-
|
|
9
|
-
const labelVariants = cva(
|
|
10
|
-
"text-sm font-bold leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
const Label = React.forwardRef<
|
|
14
|
-
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
15
|
-
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
|
16
|
-
>(({ className, ...props }, ref) => (
|
|
17
|
-
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
|
18
|
-
))
|
|
19
|
-
Label.displayName = LabelPrimitive.Root.displayName
|
|
20
|
-
|
|
21
|
-
export { Label }
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cn } from "../../../lib/utils.js";
|
|
5
|
-
import { useMediaQuery } from "../../hooks/use-media-query.js";
|
|
6
|
-
|
|
7
|
-
export type MaxWidthOption =
|
|
8
|
-
| "none"
|
|
9
|
-
| "sm"
|
|
10
|
-
| "md"
|
|
11
|
-
| "lg"
|
|
12
|
-
| "xl"
|
|
13
|
-
| "2xl"
|
|
14
|
-
| "3xl"
|
|
15
|
-
| "4xl"
|
|
16
|
-
| "5xl"
|
|
17
|
-
| "6xl"
|
|
18
|
-
| "7xl"
|
|
19
|
-
| "full";
|
|
20
|
-
|
|
21
|
-
export interface ScreenAdaptiveViewProps {
|
|
22
|
-
/** Default content (mobile + base) */
|
|
23
|
-
children: React.ReactNode;
|
|
24
|
-
|
|
25
|
-
/** Optional content to use on lg and up */
|
|
26
|
-
lgContent?: React.ReactNode;
|
|
27
|
-
|
|
28
|
-
/** Optional content to use on xl and up */
|
|
29
|
-
xlContent?: React.ReactNode;
|
|
30
|
-
|
|
31
|
-
/** Max width applied at lg (>=1024px) */
|
|
32
|
-
lgMaxWidth?: MaxWidthOption;
|
|
33
|
-
|
|
34
|
-
/** Max width applied at xl (>=1280px) */
|
|
35
|
-
xlMaxWidth?: MaxWidthOption;
|
|
36
|
-
|
|
37
|
-
/** App-only escape hatch (try to avoid) */
|
|
38
|
-
className?: string;
|
|
39
|
-
|
|
40
|
-
/** App-only escape hatch (try to avoid) */
|
|
41
|
-
innerClassName?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function widthClass(opt: MaxWidthOption | undefined) {
|
|
45
|
-
switch (opt) {
|
|
46
|
-
case "none":
|
|
47
|
-
return "screen-max-w-none";
|
|
48
|
-
case "sm":
|
|
49
|
-
return "screen-max-w-sm";
|
|
50
|
-
case "md":
|
|
51
|
-
return "screen-max-w-md";
|
|
52
|
-
case "lg":
|
|
53
|
-
return "screen-max-w-lg";
|
|
54
|
-
case "xl":
|
|
55
|
-
return "screen-max-w-xl";
|
|
56
|
-
case "2xl":
|
|
57
|
-
return "screen-max-w-2xl";
|
|
58
|
-
case "3xl":
|
|
59
|
-
return "screen-max-w-3xl";
|
|
60
|
-
case "4xl":
|
|
61
|
-
return "screen-max-w-4xl";
|
|
62
|
-
case "5xl":
|
|
63
|
-
return "screen-max-w-5xl";
|
|
64
|
-
case "6xl":
|
|
65
|
-
return "screen-max-w-6xl";
|
|
66
|
-
case "7xl":
|
|
67
|
-
return "screen-max-w-7xl";
|
|
68
|
-
case "full":
|
|
69
|
-
return "screen-max-w-full";
|
|
70
|
-
default:
|
|
71
|
-
return "screen-max-w-6xl";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function ScreenAdaptiveView({
|
|
76
|
-
children,
|
|
77
|
-
lgContent,
|
|
78
|
-
xlContent,
|
|
79
|
-
lgMaxWidth = "4xl",
|
|
80
|
-
xlMaxWidth = "6xl",
|
|
81
|
-
className,
|
|
82
|
-
innerClassName,
|
|
83
|
-
}: ScreenAdaptiveViewProps) {
|
|
84
|
-
const isLg = useMediaQuery("(min-width: 1024px)");
|
|
85
|
-
const isXl = useMediaQuery("(min-width: 1280px)");
|
|
86
|
-
|
|
87
|
-
// SSR-safe default: render base children as mobile (no max-width)
|
|
88
|
-
// After hydration, swaps to lg/xl content if provided.
|
|
89
|
-
const contentToShow =
|
|
90
|
-
(isXl && xlContent !== undefined && xlContent !== null)
|
|
91
|
-
? xlContent
|
|
92
|
-
: (isLg && lgContent !== undefined && lgContent !== null)
|
|
93
|
-
? lgContent
|
|
94
|
-
: children;
|
|
95
|
-
|
|
96
|
-
const sizeClass =
|
|
97
|
-
isXl ? widthClass(xlMaxWidth) : isLg ? widthClass(lgMaxWidth) : "screen-max-w-full";
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className={cn("screen-adaptive-root", className)}>
|
|
101
|
-
<div className={cn("screen-adaptive-inner", sizeClass, innerClassName)}>
|
|
102
|
-
{contentToShow}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
|
-
import { Button } from "./button.js";
|
|
6
|
-
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import type { ListPaginationState } from "../hooks/use-paged-list.js";
|
|
8
|
-
|
|
9
|
-
export interface ListPaginationProps {
|
|
10
|
-
/**
|
|
11
|
-
* Page state from `usePagedList` (known total),
|
|
12
|
-
* `useCursorPage().paginationFor(hasMore)` (unknown total), or any
|
|
13
|
-
* caller-owned state of the same shape.
|
|
14
|
-
*/
|
|
15
|
-
pagination: ListPaginationState;
|
|
16
|
-
/**
|
|
17
|
-
* Disables BOTH controls while the next page is in flight, without hiding the
|
|
18
|
-
* row. Server-paged surfaces pass their query's `isFetching`; a slice-paged
|
|
19
|
-
* list has nothing to wait for and omits it.
|
|
20
|
-
*/
|
|
21
|
-
busy?: boolean;
|
|
22
|
-
/** Optional extra classes. Merged with the component's own classes. */
|
|
23
|
-
className?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The ONE Previous / counter / Next control for paginated lists, in both
|
|
28
|
-
* flavors. Pair it with the hook that owns the page state (`usePagedList` for a
|
|
29
|
-
* client-side slice, `useCursorPage` — or the caller's own query state — for a
|
|
30
|
-
* cursor feed; a cursor feed passes `pager.paginationFor(hasMore)`, which binds
|
|
31
|
-
* the query's answer to these controls); this component owns only the controls,
|
|
32
|
-
* so every paginated list reads and behaves the same (ENG-002, FRONTEND-001).
|
|
33
|
-
*
|
|
34
|
-
* The counter follows the flavor: a known `totalPages` reads "2 of 5", an
|
|
35
|
-
* omitted one reads "Page 2". Everything else is identical in both flavors —
|
|
36
|
-
* which is exactly why there is one component and not two.
|
|
37
|
-
*
|
|
38
|
-
* It renders NOTHING when neither direction is available, so no call site
|
|
39
|
-
* repeats a `totalPages > 1` / `page > 1 || hasMore` guard. Edge state is real
|
|
40
|
-
* `disabled` on the buttons (announced, and unclickable), and the counter is a
|
|
41
|
-
* `status` live region so a page change is announced to screen readers.
|
|
42
|
-
*/
|
|
43
|
-
export function ListPagination({ pagination, busy = false, className }: ListPaginationProps) {
|
|
44
|
-
const { currentPage, totalPages, canPreviousPage, canNextPage, goToPreviousPage, goToNextPage } =
|
|
45
|
-
pagination;
|
|
46
|
-
|
|
47
|
-
// The control that started the in-flight page shows the spinner. Cleared when `busy`
|
|
48
|
-
// ends — a render-phase adjustment, so there is no effect and no stale frame.
|
|
49
|
-
const [clicked, setClicked] = useState<"previous" | "next" | null>(null);
|
|
50
|
-
const [wasBusy, setWasBusy] = useState(busy);
|
|
51
|
-
if (busy !== wasBusy) {
|
|
52
|
-
setWasBusy(busy);
|
|
53
|
-
if (!busy) setClicked(null);
|
|
54
|
-
}
|
|
55
|
-
const previousPending = busy && clicked === "previous";
|
|
56
|
-
const nextPending = busy && clicked === "next";
|
|
57
|
-
|
|
58
|
-
// One visibility rule for both flavors. For a known total this is exactly
|
|
59
|
-
// `totalPages > 1`: page state is clamped into `1..totalPages`, so a
|
|
60
|
-
// single-page list has no reachable neighbour in either direction, and a
|
|
61
|
-
// multi-page list always has one. For a cursor feed it is the hand-rolled
|
|
62
|
-
// `page > 1 || hasMore`. `busy` deliberately does not hide the row — the
|
|
63
|
-
// controls grey out in place instead of vanishing mid-fetch.
|
|
64
|
-
if (!canPreviousPage && !canNextPage) return null;
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className={cn("flex w-full items-center justify-between gap-4 mt-4", className)}>
|
|
68
|
-
<Button
|
|
69
|
-
variant="outline"
|
|
70
|
-
size="sm"
|
|
71
|
-
onClick={() => {
|
|
72
|
-
setClicked("previous");
|
|
73
|
-
goToPreviousPage();
|
|
74
|
-
}}
|
|
75
|
-
disabled={busy || !canPreviousPage}
|
|
76
|
-
pending={previousPending}
|
|
77
|
-
icon={<ChevronLeft className="icon-xs" />}
|
|
78
|
-
className="h-11 min-w-11"
|
|
79
|
-
>
|
|
80
|
-
Previous
|
|
81
|
-
</Button>
|
|
82
|
-
<span className="text-counter" role="status">
|
|
83
|
-
{totalPages === undefined ? `Page ${currentPage}` : `${currentPage} of ${totalPages}`}
|
|
84
|
-
</span>
|
|
85
|
-
<Button
|
|
86
|
-
variant="outline"
|
|
87
|
-
size="sm"
|
|
88
|
-
onClick={() => {
|
|
89
|
-
setClicked("next");
|
|
90
|
-
goToNextPage();
|
|
91
|
-
}}
|
|
92
|
-
disabled={busy || !canNextPage}
|
|
93
|
-
pending={nextPending}
|
|
94
|
-
className="h-11 min-w-11"
|
|
95
|
-
>
|
|
96
|
-
Next
|
|
97
|
-
{nextPending ? null : <ChevronRight className="icon-xs" />}
|
|
98
|
-
</Button>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
}
|