@gv-tech/ui-web 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +88 -0
- package/src/accordion.tsx +58 -0
- package/src/alert-dialog.tsx +121 -0
- package/src/alert.tsx +49 -0
- package/src/aspect-ratio.tsx +7 -0
- package/src/avatar.tsx +40 -0
- package/src/badge.tsx +34 -0
- package/src/breadcrumb.tsx +105 -0
- package/src/button.tsx +47 -0
- package/src/calendar.tsx +163 -0
- package/src/card.tsx +46 -0
- package/src/carousel.tsx +234 -0
- package/src/chart.tsx +296 -0
- package/src/checkbox.tsx +31 -0
- package/src/collapsible.tsx +15 -0
- package/src/command.tsx +154 -0
- package/src/context-menu.tsx +208 -0
- package/src/dialog.tsx +95 -0
- package/src/drawer.tsx +110 -0
- package/src/dropdown-menu.tsx +212 -0
- package/src/form.tsx +160 -0
- package/src/hooks/use-theme.ts +15 -0
- package/src/hooks/use-toast.ts +189 -0
- package/src/hover-card.tsx +35 -0
- package/src/index.ts +474 -0
- package/src/input.tsx +23 -0
- package/src/label.tsx +21 -0
- package/src/lib/utils.ts +6 -0
- package/src/menubar.tsx +244 -0
- package/src/navigation-menu.tsx +143 -0
- package/src/pagination.tsx +107 -0
- package/src/popover.tsx +45 -0
- package/src/progress.tsx +28 -0
- package/src/radio-group.tsx +41 -0
- package/src/resizable.tsx +59 -0
- package/src/scroll-area.tsx +42 -0
- package/src/search.tsx +87 -0
- package/src/select.tsx +169 -0
- package/src/separator.tsx +24 -0
- package/src/setupTests.ts +114 -0
- package/src/sheet.tsx +136 -0
- package/src/skeleton.tsx +10 -0
- package/src/slider.tsx +27 -0
- package/src/sonner.tsx +32 -0
- package/src/switch.tsx +31 -0
- package/src/table.tsx +104 -0
- package/src/tabs.tsx +62 -0
- package/src/text.tsx +55 -0
- package/src/textarea.tsx +25 -0
- package/src/theme-provider.tsx +15 -0
- package/src/theme-toggle.tsx +92 -0
- package/src/toast.tsx +111 -0
- package/src/toaster.tsx +27 -0
- package/src/toggle-group.tsx +55 -0
- package/src/toggle.tsx +24 -0
- package/src/tooltip.tsx +51 -0
package/src/form.tsx
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
4
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
|
+
import * as React from 'react';
|
|
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 {
|
|
16
|
+
FormControlBaseProps,
|
|
17
|
+
FormDescriptionBaseProps,
|
|
18
|
+
FormItemBaseProps,
|
|
19
|
+
FormLabelBaseProps,
|
|
20
|
+
FormMessageBaseProps,
|
|
21
|
+
} from '@gv-tech/ui-core';
|
|
22
|
+
import { Label } from './label';
|
|
23
|
+
import { cn } from './lib/utils';
|
|
24
|
+
|
|
25
|
+
const Form = FormProvider;
|
|
26
|
+
|
|
27
|
+
type FormFieldContextValue<
|
|
28
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
29
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
30
|
+
> = {
|
|
31
|
+
name: TName;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const FormFieldContext = React.createContext<FormFieldContextValue | null>(null);
|
|
35
|
+
|
|
36
|
+
const FormField = <
|
|
37
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
38
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
39
|
+
>({
|
|
40
|
+
...props
|
|
41
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
42
|
+
return (
|
|
43
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
44
|
+
<Controller {...props} />
|
|
45
|
+
</FormFieldContext.Provider>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const useFormField = () => {
|
|
50
|
+
const fieldContext = React.useContext(FormFieldContext);
|
|
51
|
+
const itemContext = React.useContext(FormItemContext);
|
|
52
|
+
const { getFieldState, formState } = useFormContext();
|
|
53
|
+
|
|
54
|
+
if (!fieldContext) {
|
|
55
|
+
throw new Error('useFormField should be used within <FormField>');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!itemContext) {
|
|
59
|
+
throw new Error('useFormField should be used within <FormItem>');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fieldState = getFieldState(fieldContext.name, formState);
|
|
63
|
+
|
|
64
|
+
const { id } = itemContext;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
name: fieldContext.name,
|
|
69
|
+
formItemId: `${id}-form-item`,
|
|
70
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
71
|
+
formMessageId: `${id}-form-item-message`,
|
|
72
|
+
...fieldState,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type FormItemContextValue = {
|
|
77
|
+
id: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const FormItemContext = React.createContext<FormItemContextValue | null>(null);
|
|
81
|
+
|
|
82
|
+
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & FormItemBaseProps>(
|
|
83
|
+
({ className, ...props }, ref) => {
|
|
84
|
+
const id = React.useId();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<FormItemContext.Provider value={{ id }}>
|
|
88
|
+
<div ref={ref} className={cn('space-y-2', className)} {...props} />
|
|
89
|
+
</FormItemContext.Provider>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
FormItem.displayName = 'FormItem';
|
|
94
|
+
|
|
95
|
+
const FormLabel = React.forwardRef<
|
|
96
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
97
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & FormLabelBaseProps
|
|
98
|
+
>(({ className, ...props }, ref) => {
|
|
99
|
+
const { error, formItemId } = useFormField();
|
|
100
|
+
|
|
101
|
+
return <Label ref={ref} className={cn(error && 'text-destructive', className)} htmlFor={formItemId} {...props} />;
|
|
102
|
+
});
|
|
103
|
+
FormLabel.displayName = 'FormLabel';
|
|
104
|
+
|
|
105
|
+
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
|
106
|
+
({ ...props }, ref) => {
|
|
107
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Slot
|
|
111
|
+
ref={ref}
|
|
112
|
+
id={formItemId}
|
|
113
|
+
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
|
114
|
+
aria-invalid={!!error}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
FormControl.displayName = 'FormControl';
|
|
121
|
+
|
|
122
|
+
const FormDescription = React.forwardRef<
|
|
123
|
+
HTMLParagraphElement,
|
|
124
|
+
React.HTMLAttributes<HTMLParagraphElement> & FormDescriptionBaseProps
|
|
125
|
+
>(({ className, ...props }, ref) => {
|
|
126
|
+
const { formDescriptionId } = useFormField();
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<p ref={ref} id={formDescriptionId} className={cn('text-[0.8rem] text-muted-foreground', className)} {...props} />
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
FormDescription.displayName = 'FormDescription';
|
|
133
|
+
|
|
134
|
+
const FormMessage = React.forwardRef<
|
|
135
|
+
HTMLParagraphElement,
|
|
136
|
+
React.HTMLAttributes<HTMLParagraphElement> & FormMessageBaseProps
|
|
137
|
+
>(({ className, children, ...props }, ref) => {
|
|
138
|
+
const { error, formMessageId } = useFormField();
|
|
139
|
+
const body = error ? String(error?.message ?? '') : children;
|
|
140
|
+
|
|
141
|
+
if (!body) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<p ref={ref} id={formMessageId} className={cn('text-[0.8rem] font-medium text-destructive', className)} {...props}>
|
|
147
|
+
{body}
|
|
148
|
+
</p>
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
FormMessage.displayName = 'FormMessage';
|
|
152
|
+
|
|
153
|
+
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
|
|
154
|
+
export type {
|
|
155
|
+
FormControlBaseProps as FormControlProps,
|
|
156
|
+
FormDescriptionBaseProps as FormDescriptionProps,
|
|
157
|
+
FormItemBaseProps as FormItemProps,
|
|
158
|
+
FormLabelBaseProps as FormLabelProps,
|
|
159
|
+
FormMessageBaseProps as FormMessageProps,
|
|
160
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { theme } from '@gv-tech/design-tokens';
|
|
2
|
+
import { useTheme as useNextTheme } from 'next-themes';
|
|
3
|
+
|
|
4
|
+
export function useTheme() {
|
|
5
|
+
const context = useNextTheme();
|
|
6
|
+
const { resolvedTheme } = context;
|
|
7
|
+
|
|
8
|
+
// Default to light theme tokens if resolvedTheme is undefined or invalid
|
|
9
|
+
const activeTokens = resolvedTheme === 'dark' ? theme.dark : theme.light;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...context,
|
|
13
|
+
tokens: activeTokens,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Inspired by react-hot-toast library
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import type { ToastActionElement, ToastProps } from '../index';
|
|
7
|
+
|
|
8
|
+
const TOAST_LIMIT = 1;
|
|
9
|
+
const TOAST_REMOVE_DELAY = 1000000;
|
|
10
|
+
|
|
11
|
+
type ToasterToast = ToastProps & {
|
|
12
|
+
id: string;
|
|
13
|
+
title?: React.ReactNode;
|
|
14
|
+
description?: React.ReactNode;
|
|
15
|
+
action?: ToastActionElement;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const actionTypes = {
|
|
19
|
+
ADD_TOAST: 'ADD_TOAST',
|
|
20
|
+
UPDATE_TOAST: 'UPDATE_TOAST',
|
|
21
|
+
DISMISS_TOAST: 'DISMISS_TOAST',
|
|
22
|
+
REMOVE_TOAST: 'REMOVE_TOAST',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
let count = 0;
|
|
26
|
+
|
|
27
|
+
function genId() {
|
|
28
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
29
|
+
return count.toString();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type ActionType = typeof actionTypes;
|
|
33
|
+
|
|
34
|
+
type Action =
|
|
35
|
+
| {
|
|
36
|
+
type: ActionType['ADD_TOAST'];
|
|
37
|
+
toast: ToasterToast;
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
type: ActionType['UPDATE_TOAST'];
|
|
41
|
+
toast: Partial<ToasterToast>;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
type: ActionType['DISMISS_TOAST'];
|
|
45
|
+
toastId?: ToasterToast['id'];
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
type: ActionType['REMOVE_TOAST'];
|
|
49
|
+
toastId?: ToasterToast['id'];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
interface State {
|
|
53
|
+
toasts: ToasterToast[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
57
|
+
|
|
58
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
59
|
+
if (toastTimeouts.has(toastId)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const timeout = setTimeout(() => {
|
|
64
|
+
toastTimeouts.delete(toastId);
|
|
65
|
+
dispatch({
|
|
66
|
+
type: 'REMOVE_TOAST',
|
|
67
|
+
toastId: toastId,
|
|
68
|
+
});
|
|
69
|
+
}, TOAST_REMOVE_DELAY);
|
|
70
|
+
|
|
71
|
+
toastTimeouts.set(toastId, timeout);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const reducer = (state: State, action: Action): State => {
|
|
75
|
+
switch (action.type) {
|
|
76
|
+
case actionTypes.ADD_TOAST:
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
case actionTypes.UPDATE_TOAST:
|
|
83
|
+
return {
|
|
84
|
+
...state,
|
|
85
|
+
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
case actionTypes.DISMISS_TOAST: {
|
|
89
|
+
const { toastId } = action;
|
|
90
|
+
|
|
91
|
+
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
|
92
|
+
// but I'll keep it here for simplicity
|
|
93
|
+
if (toastId) {
|
|
94
|
+
addToRemoveQueue(toastId);
|
|
95
|
+
} else {
|
|
96
|
+
state.toasts.forEach((toast) => {
|
|
97
|
+
addToRemoveQueue(toast.id);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...state,
|
|
103
|
+
toasts: state.toasts.map((t) =>
|
|
104
|
+
t.id === toastId || toastId === undefined
|
|
105
|
+
? {
|
|
106
|
+
...t,
|
|
107
|
+
open: false,
|
|
108
|
+
}
|
|
109
|
+
: t,
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
case actionTypes.REMOVE_TOAST:
|
|
114
|
+
if (action.toastId === undefined) {
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
toasts: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
...state,
|
|
122
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const listeners: Array<(state: State) => void> = [];
|
|
128
|
+
|
|
129
|
+
let memoryState: State = { toasts: [] };
|
|
130
|
+
|
|
131
|
+
function dispatch(action: Action) {
|
|
132
|
+
memoryState = reducer(memoryState, action);
|
|
133
|
+
listeners.forEach((listener) => {
|
|
134
|
+
listener(memoryState);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type Toast = Omit<ToasterToast, 'id'>;
|
|
139
|
+
|
|
140
|
+
function toast({ ...props }: Toast) {
|
|
141
|
+
const id = genId();
|
|
142
|
+
|
|
143
|
+
const update = (props: ToasterToast) =>
|
|
144
|
+
dispatch({
|
|
145
|
+
type: 'UPDATE_TOAST',
|
|
146
|
+
toast: { ...props, id },
|
|
147
|
+
});
|
|
148
|
+
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
|
|
149
|
+
|
|
150
|
+
dispatch({
|
|
151
|
+
type: 'ADD_TOAST',
|
|
152
|
+
toast: {
|
|
153
|
+
...props,
|
|
154
|
+
id,
|
|
155
|
+
open: true,
|
|
156
|
+
onOpenChange: (open) => {
|
|
157
|
+
if (!open) dismiss();
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
id: id,
|
|
164
|
+
dismiss,
|
|
165
|
+
update,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function useToast() {
|
|
170
|
+
const [state, setState] = React.useState<State>(memoryState);
|
|
171
|
+
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
listeners.push(setState);
|
|
174
|
+
return () => {
|
|
175
|
+
const index = listeners.indexOf(setState);
|
|
176
|
+
if (index > -1) {
|
|
177
|
+
listeners.splice(index, 1);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}, [state]);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
...state,
|
|
184
|
+
toast,
|
|
185
|
+
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { toast, useToast };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
import { HoverCardBaseProps, HoverCardContentBaseProps, HoverCardTriggerBaseProps } from '@gv-tech/ui-core';
|
|
7
|
+
import { cn } from './lib/utils';
|
|
8
|
+
|
|
9
|
+
const HoverCard = HoverCardPrimitive.Root;
|
|
10
|
+
|
|
11
|
+
const HoverCardTrigger = HoverCardPrimitive.Trigger;
|
|
12
|
+
|
|
13
|
+
const HoverCardContent = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> & HoverCardContentBaseProps
|
|
16
|
+
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
|
17
|
+
<HoverCardPrimitive.Content
|
|
18
|
+
ref={ref}
|
|
19
|
+
align={align}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 origin-[--radix-hover-card-content-transform-origin]',
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
));
|
|
28
|
+
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
|
|
29
|
+
|
|
30
|
+
export { HoverCard, HoverCardContent, HoverCardTrigger };
|
|
31
|
+
export type {
|
|
32
|
+
HoverCardContentBaseProps as HoverCardContentProps,
|
|
33
|
+
HoverCardBaseProps as HoverCardProps,
|
|
34
|
+
HoverCardTriggerBaseProps as HoverCardTriggerProps,
|
|
35
|
+
};
|