Update Convex with no payload to be just like convex with payload but without payload
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-10982
File diff suppressed because one or more lines are too long
@@ -12,23 +12,27 @@
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --flag unstable_native_nodejs_ts_config",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
|
||||
"test:unit": "vitest run --project unit --passWithNoTests",
|
||||
"test:integration": "vitest run --project integration --passWithNoTests",
|
||||
"test:component": "NODE_ENV=test vitest run --project component",
|
||||
"ui-add": "bunx --bun shadcn@latest add && prettier src --write --list-different"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.3.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@tabler/icons-react": "^3.41.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@@ -36,26 +40,31 @@
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.577.0",
|
||||
"motion": "^12.38.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react-day-picker": "^9.14.0",
|
||||
"react-hook-form": "^7.65.0",
|
||||
"react-hook-form": "^7.72.0",
|
||||
"react-image-crop": "^11.0.10",
|
||||
"react-resizable-panels": "^4",
|
||||
"recharts": "^3.8.0",
|
||||
"react-resizable-panels": "^4.7.6",
|
||||
"recharts": "^3.8.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"vaul": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gib/eslint-config": "workspace:*",
|
||||
"@gib/prettier-config": "workspace:*",
|
||||
"@gib/tsconfig": "workspace:*",
|
||||
"@gib/vitest-config": "workspace:*",
|
||||
"@testing-library/react": "catalog:test",
|
||||
"@types/react": "catalog:react19",
|
||||
"eslint": "catalog:",
|
||||
"jsdom": "catalog:test",
|
||||
"prettier": "catalog:",
|
||||
"react": "catalog:react19",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:test",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -4,12 +4,12 @@ import type { ComponentProps } from 'react';
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
import { AvatarImage, cn } from '@gib/ui';
|
||||
import { cn } from '@gib/ui';
|
||||
|
||||
type BasedAvatarProps = ComponentProps<typeof AvatarPrimitive.Root> & {
|
||||
src?: string | null;
|
||||
fullName?: string | null;
|
||||
imageProps?: Omit<ComponentProps<typeof AvatarImage>, 'data-slot'>;
|
||||
imageProps?: Omit<ComponentProps<typeof AvatarPrimitive.Image>, 'data-slot'>;
|
||||
fallbackProps?: ComponentProps<typeof AvatarPrimitive.Fallback>;
|
||||
userIconProps?: ComponentProps<typeof User>;
|
||||
};
|
||||
@@ -35,7 +35,7 @@ const BasedAvatar = ({
|
||||
{...props}
|
||||
>
|
||||
{src ? (
|
||||
<AvatarImage
|
||||
<AvatarPrimitive.Image
|
||||
{...imageProps}
|
||||
src={src}
|
||||
className={imageProps?.className}
|
||||
|
||||
@@ -45,7 +45,7 @@ const BasedProgress = ({
|
||||
<ProgressPrimitive.Indicator
|
||||
data-slot='progress-indicator'
|
||||
className='bg-primary h-full w-full flex-1 transition-all'
|
||||
style={{ transform: `translateX(-${100 - (progress ?? 0)}%)` }}
|
||||
style={{ transform: `translateX(-${100 - progress}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
);
|
||||
|
||||
@@ -98,7 +98,7 @@ const Carousel = ({
|
||||
api.on('select', onSelect);
|
||||
|
||||
return () => {
|
||||
api?.off('select', onSelect);
|
||||
api.off('select', onSelect);
|
||||
};
|
||||
}, [api, onSelect]);
|
||||
|
||||
@@ -108,8 +108,7 @@ const Carousel = ({
|
||||
carouselRef,
|
||||
api: api,
|
||||
opts,
|
||||
orientation:
|
||||
orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
|
||||
orientation: orientation,
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
canScrollPrev,
|
||||
|
||||
+51
-26
@@ -48,7 +48,7 @@ const ChartContainer = ({
|
||||
>['children'];
|
||||
}) => {
|
||||
const uniqueId = React.useId();
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
|
||||
const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
@@ -72,7 +72,7 @@ const ChartContainer = ({
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color,
|
||||
([, config]) => config.theme ?? config.color,
|
||||
);
|
||||
|
||||
if (!colorConfig.length) {
|
||||
@@ -89,7 +89,7 @@ ${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
|
||||
itemConfig.color;
|
||||
return color ? ` --color-${key}: ${color};` : null;
|
||||
})
|
||||
@@ -105,6 +105,15 @@ ${colorConfig
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip;
|
||||
|
||||
type ChartPayloadItem = {
|
||||
name?: string | number;
|
||||
value?: number | string;
|
||||
dataKey?: string | number;
|
||||
type?: string;
|
||||
color?: string;
|
||||
payload?: Record<string, unknown> & { fill?: string };
|
||||
};
|
||||
|
||||
const ChartTooltipContent = ({
|
||||
active,
|
||||
payload,
|
||||
@@ -119,14 +128,29 @@ const ChartTooltipContent = ({
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<'div'> & {
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: 'line' | 'dot' | 'dashed';
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}) => {
|
||||
}: React.ComponentProps<'div'> & {
|
||||
active?: boolean;
|
||||
payload?: ChartPayloadItem[];
|
||||
label?: React.ReactNode;
|
||||
labelFormatter?: (
|
||||
value: React.ReactNode,
|
||||
payload: ChartPayloadItem[],
|
||||
) => React.ReactNode;
|
||||
formatter?: (
|
||||
value: number | string,
|
||||
name: string | number,
|
||||
item: ChartPayloadItem,
|
||||
index: number,
|
||||
itemPayload: ChartPayloadItem['payload'],
|
||||
) => React.ReactNode;
|
||||
color?: string;
|
||||
labelClassName?: string;
|
||||
hideLabel?: boolean;
|
||||
hideIndicator?: boolean;
|
||||
indicator?: 'line' | 'dot' | 'dashed';
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
}) => {
|
||||
const { config } = useChart();
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
@@ -135,11 +159,11 @@ const ChartTooltipContent = ({
|
||||
}
|
||||
|
||||
const [item] = payload;
|
||||
const key = `${labelKey || item?.dataKey || item?.name || 'value'}`;
|
||||
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const value =
|
||||
!labelKey && typeof label === 'string'
|
||||
? config[label]?.label || label
|
||||
? (config[label]?.label ?? label)
|
||||
: itemConfig?.label;
|
||||
|
||||
if (labelFormatter) {
|
||||
@@ -183,19 +207,19 @@ const ChartTooltipContent = ({
|
||||
{payload
|
||||
.filter((item) => item.type !== 'none')
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || 'value'}`;
|
||||
const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const indicatorColor = color || item.payload.fill || item.color;
|
||||
const indicatorColor = color ?? item.payload?.fill ?? item.color;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
key={item.dataKey ?? index}
|
||||
className={cn(
|
||||
'[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',
|
||||
indicator === 'dot' && 'items-center',
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
{formatter && item.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
@@ -232,7 +256,7 @@ const ChartTooltipContent = ({
|
||||
<div className='grid gap-1.5'>
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className='text-muted-foreground'>
|
||||
{itemConfig?.label || item.name}
|
||||
{itemConfig?.label ?? item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
@@ -259,11 +283,12 @@ const ChartLegendContent = ({
|
||||
payload,
|
||||
verticalAlign = 'bottom',
|
||||
nameKey,
|
||||
}: React.ComponentProps<'div'> &
|
||||
Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
}) => {
|
||||
}: React.ComponentProps<'div'> & {
|
||||
payload?: ChartPayloadItem[];
|
||||
verticalAlign?: 'top' | 'bottom';
|
||||
hideIcon?: boolean;
|
||||
nameKey?: string;
|
||||
}) => {
|
||||
const { config } = useChart();
|
||||
|
||||
if (!payload?.length) {
|
||||
@@ -280,13 +305,13 @@ const ChartLegendContent = ({
|
||||
>
|
||||
{payload
|
||||
.filter((item) => item.type !== 'none')
|
||||
.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || 'value'}`;
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey ?? item.dataKey ?? 'value'}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
key={item.value ?? index}
|
||||
className={cn(
|
||||
'[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3',
|
||||
)}
|
||||
|
||||
@@ -68,13 +68,16 @@ const ComboboxInput = ({
|
||||
/>
|
||||
<InputGroupAddon align='inline-end'>
|
||||
{showTrigger && (
|
||||
<InputGroupButton
|
||||
size='icon-xs'
|
||||
variant='ghost'
|
||||
render={<ComboboxTrigger />}
|
||||
data-slot='input-group-button'
|
||||
className='group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent'
|
||||
disabled={disabled}
|
||||
<ComboboxTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
size='icon-xs'
|
||||
variant='ghost'
|
||||
data-slot='input-group-button'
|
||||
className='group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent'
|
||||
disabled={disabled}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showClear && <ComboboxClear disabled={disabled} />}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare module '*.css';
|
||||
@@ -46,10 +46,6 @@ const useFormField = () => {
|
||||
const formState = useFormState({ name: fieldContext.name });
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error('useFormField should be used within <FormField>');
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
@@ -138,7 +134,7 @@ const FormDescription = ({
|
||||
|
||||
const FormMessage = ({ className, ...props }: React.ComponentProps<'p'>) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message ?? '') : props.children;
|
||||
const body = error ? String(error.message ?? '') : props.children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { MousePointerClick, X } from 'lucide-react';
|
||||
|
||||
type EventType =
|
||||
| 'mousedown'
|
||||
|
||||
@@ -153,7 +153,7 @@ export const ImageCrop = ({
|
||||
useEffect(() => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () =>
|
||||
setImgSrc(reader.result?.toString() || ''),
|
||||
setImgSrc(typeof reader.result === 'string' ? reader.result : ''),
|
||||
);
|
||||
reader.readAsDataURL(file);
|
||||
}, [file]);
|
||||
@@ -173,12 +173,13 @@ export const ImageCrop = ({
|
||||
onChange?.(pixelCrop, percentCrop);
|
||||
};
|
||||
|
||||
const handleComplete = async (
|
||||
const handleComplete = (
|
||||
pixelCrop: PixelCrop,
|
||||
percentCrop: PercentCrop,
|
||||
) => {
|
||||
): Promise<void> => {
|
||||
setCompletedCrop(pixelCrop);
|
||||
onComplete?.(pixelCrop, percentCrop);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const applyCrop = async () => {
|
||||
@@ -293,19 +294,17 @@ export const ImageCropApply = ({
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot.Root onClick={handleClick} {...(props as any)}>
|
||||
<Slot.Root
|
||||
onClick={handleClick}
|
||||
{...(props as ComponentProps<typeof Slot.Root>)}
|
||||
>
|
||||
{children}
|
||||
</Slot.Root>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size='icon'
|
||||
variant='ghost'
|
||||
{...(props as any)}
|
||||
>
|
||||
<Button onClick={handleClick} size='icon' variant='ghost' {...props}>
|
||||
{children ?? <CropIcon className='size-4' />}
|
||||
</Button>
|
||||
);
|
||||
@@ -330,19 +329,17 @@ export const ImageCropReset = ({
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot.Root onClick={handleClick} {...(props as any)}>
|
||||
<Slot.Root
|
||||
onClick={handleClick}
|
||||
{...(props as ComponentProps<typeof Slot.Root>)}
|
||||
>
|
||||
{children}
|
||||
</Slot.Root>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size='icon'
|
||||
variant='ghost'
|
||||
{...(props as any)}
|
||||
>
|
||||
<Button onClick={handleClick} size='icon' variant='ghost' {...props}>
|
||||
{children ?? <RotateCcwIcon className='size-4' />}
|
||||
</Button>
|
||||
);
|
||||
@@ -372,7 +369,15 @@ export const Cropper = ({
|
||||
onChange={onChange}
|
||||
onComplete={onComplete}
|
||||
onCrop={onCrop}
|
||||
{...(props as any)}
|
||||
{...(props as Omit<
|
||||
ImageCropProps,
|
||||
| 'file'
|
||||
| 'maxImageSize'
|
||||
| 'onChange'
|
||||
| 'onComplete'
|
||||
| 'onCrop'
|
||||
| 'children'
|
||||
>)}
|
||||
>
|
||||
<ImageCropContent className={className} style={style} />
|
||||
</ImageCrop>
|
||||
|
||||
@@ -47,7 +47,7 @@ const InputOTPSlot = ({
|
||||
index: number;
|
||||
}) => {
|
||||
const inputOTPContext = React.useContext(OTPInputContext);
|
||||
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
||||
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] ?? {};
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -21,7 +21,7 @@ const Progress = ({
|
||||
<ProgressPrimitive.Indicator
|
||||
data-slot='progress-indicator'
|
||||
className='bg-primary size-full flex-1 transition-all'
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
);
|
||||
|
||||
@@ -602,9 +602,9 @@ const SidebarMenuSkeleton = ({
|
||||
showIcon?: boolean;
|
||||
}) => {
|
||||
// Random width between 50 to 90%.
|
||||
const width = React.useMemo(() => {
|
||||
return `${Math.floor(Math.random() * 40) + 50}%`;
|
||||
}, []);
|
||||
const [width] = React.useState(
|
||||
() => `${Math.floor(Math.random() * 40) + 50}%`,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -58,13 +58,13 @@ const ToggleGroupItem = ({
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
data-slot='toggle-group-item'
|
||||
data-variant={context.variant || variant}
|
||||
data-size={context.size || size}
|
||||
data-variant={context.variant ?? variant}
|
||||
data-size={context.size ?? size}
|
||||
data-spacing={context.spacing}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
variant: context.variant ?? variant,
|
||||
size: context.size ?? size,
|
||||
}),
|
||||
'w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10',
|
||||
'data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Button } from '../../src/button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders its children', () => {
|
||||
render(<Button>Click me</Button>);
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Click me' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
@@ -5,6 +5,6 @@
|
||||
"jsx": "preserve",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "tests", "vitest.config.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
import { jsdomProject, nodeProject } from '@gib/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
nodeProject('unit', ['tests/unit/**/*.test.{ts,tsx}']),
|
||||
nodeProject('integration', ['tests/integration/**/*.test.{ts,tsx}']),
|
||||
jsdomProject('component', ['tests/component/**/*.test.{ts,tsx}']),
|
||||
],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user