mirror of https://github.com/buster-so/buster.git
Query request in line
This commit is contained in:
parent
969fdc6b18
commit
8842720eae
|
@ -1,13 +1,24 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { keepPreviousData, useQuery, type UseQueryOptions } from '@tanstack/react-query';
|
||||
import { userQueryKeys } from '@/api/query_keys/users';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { getUserToOrganization } from './requests';
|
||||
import type { GetUserToOrganizationResponse } from '@buster/server-shared/user';
|
||||
import type { RustApiError } from '../../errors';
|
||||
|
||||
export const useGetUserToOrganization = (params: Parameters<typeof getUserToOrganization>[0]) => {
|
||||
export const useGetUserToOrganization = <TData = GetUserToOrganizationResponse>(
|
||||
params: Parameters<typeof getUserToOrganization>[0],
|
||||
options?: Omit<
|
||||
UseQueryOptions<GetUserToOrganizationResponse, RustApiError, TData>,
|
||||
'queryKey' | 'queryFn'
|
||||
>
|
||||
) => {
|
||||
const queryFn = useMemoizedFn(() => getUserToOrganization(params));
|
||||
|
||||
return useQuery({
|
||||
...userQueryKeys.userGetUserToOrganization(params),
|
||||
queryFn
|
||||
placeholderData: keepPreviousData,
|
||||
queryFn,
|
||||
select: options?.select,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,6 +6,6 @@ import {
|
|||
|
||||
export const getUserToOrganization = async (payload: GetUserToOrganizationRequest) => {
|
||||
return mainApiV2
|
||||
.get<GetUserToOrganizationResponse>('/users/organization', { params: payload })
|
||||
.get<GetUserToOrganizationResponse>('/users', { params: payload })
|
||||
.then((response) => response.data);
|
||||
};
|
||||
|
|
|
@ -58,7 +58,8 @@ const userGetUserDatasetGroups = (userId: string) =>
|
|||
|
||||
const userGetUserToOrganization = (params: GetUserToOrganizationRequest) =>
|
||||
queryOptions<GetUserToOrganizationResponse>({
|
||||
queryKey: ['users', 'organization', params] as const
|
||||
queryKey: ['users', 'organization', params] as const,
|
||||
staleTime: 10 * 1000
|
||||
});
|
||||
|
||||
export const userQueryKeys = {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Text } from '@/components/ui/typography';
|
|||
import { useMemoizedFn } from '@/hooks';
|
||||
import { AccessDropdown } from './AccessDropdown';
|
||||
import type { ShareAssetType, ShareRole } from '@buster/server-shared/share';
|
||||
import { AvatarUserButton } from '../../ui/avatar/AvatarUserButton';
|
||||
|
||||
export const IndividualSharePerson: React.FC<{
|
||||
name?: string;
|
||||
|
@ -24,22 +25,7 @@ export const IndividualSharePerson: React.FC<{
|
|||
<div
|
||||
className="flex h-8 items-center justify-between space-x-2 overflow-hidden"
|
||||
data-testid={`share-person-${email}`}>
|
||||
<div className="flex h-full items-center space-x-1.5 overflow-hidden">
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<Avatar className="h-[24px] w-[24px]" name={name || email} image={avatar_url} />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-0 overflow-hidden">
|
||||
<Text truncate className="leading-1.3">
|
||||
{name || email}
|
||||
</Text>
|
||||
|
||||
{isSameEmailName ? null : (
|
||||
<Text truncate size="xs" variant="tertiary" className="leading-1.3">
|
||||
{email}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<AvatarUserButton username={name} email={email} avatarUrl={avatar_url} avatarSize={24} />
|
||||
|
||||
<AccessDropdown
|
||||
shareLevel={role}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { InputSearchDropdown } from '@/components/ui/inputs/InputSearchDropdown';
|
||||
import type { ShareAssetType, ShareConfig, ShareRole } from '@buster/server-shared/share';
|
||||
import { AccessDropdown } from './AccessDropdown';
|
||||
|
@ -10,6 +10,9 @@ import { useMemoizedFn } from '@/hooks';
|
|||
import { useShareMetric } from '@/api/buster_rest/metrics';
|
||||
import { useShareDashboard } from '@/api/buster_rest/dashboards';
|
||||
import { useShareCollection } from '@/api/buster_rest/collections';
|
||||
import { useGetUserToOrganization } from '../../../api/buster_rest/users';
|
||||
import type { SelectItem } from '../../ui/select';
|
||||
import { AvatarUserButton } from '../../ui/avatar/AvatarUserButton';
|
||||
|
||||
interface ShareMenuInviteProps {
|
||||
assetType: ShareAssetType;
|
||||
|
@ -17,96 +20,136 @@ interface ShareMenuInviteProps {
|
|||
individualPermissions: ShareConfig['individual_permissions'];
|
||||
}
|
||||
|
||||
export const ShareMenuInvite: React.FC<ShareMenuInviteProps> = ({
|
||||
individualPermissions,
|
||||
assetType,
|
||||
assetId
|
||||
}) => {
|
||||
const { openErrorMessage } = useBusterNotifications();
|
||||
export const ShareMenuInvite: React.FC<ShareMenuInviteProps> = React.memo(
|
||||
({ individualPermissions, assetType, assetId }) => {
|
||||
const { openErrorMessage } = useBusterNotifications();
|
||||
|
||||
const { mutateAsync: onShareMetric, isPending: isInvitingMetric } = useShareMetric();
|
||||
const { mutateAsync: onShareDashboard, isPending: isInvitingDashboard } = useShareDashboard();
|
||||
const { mutateAsync: onShareCollection, isPending: isInvitingCollection } = useShareCollection();
|
||||
const { mutateAsync: onShareMetric, isPending: isInvitingMetric } = useShareMetric();
|
||||
const { mutateAsync: onShareDashboard, isPending: isInvitingDashboard } = useShareDashboard();
|
||||
const { mutateAsync: onShareCollection, isPending: isInvitingCollection } =
|
||||
useShareCollection();
|
||||
|
||||
const [inputValue, setInputValue] = React.useState<string>('');
|
||||
const [defaultPermissionLevel, setDefaultPermissionLevel] = React.useState<ShareRole>('canView');
|
||||
const [inputValue, setInputValue] = React.useState<string>('');
|
||||
const [defaultPermissionLevel, setDefaultPermissionLevel] =
|
||||
React.useState<ShareRole>('canView');
|
||||
|
||||
const disableSubmit = !inputHasText(inputValue) || !isValidEmail(inputValue);
|
||||
const isInviting = isInvitingMetric || isInvitingDashboard || isInvitingCollection;
|
||||
const { data: usersData } = useGetUserToOrganization({
|
||||
user_name: inputValue,
|
||||
email: inputValue,
|
||||
page: 1,
|
||||
page_size: 5
|
||||
});
|
||||
|
||||
const onChangeAccessDropdown = useMemoizedFn((level: ShareRole | null) => {
|
||||
if (level) setDefaultPermissionLevel(level);
|
||||
});
|
||||
const disableSubmit = !inputHasText(inputValue) || !isValidEmail(inputValue);
|
||||
const isInviting = isInvitingMetric || isInvitingDashboard || isInvitingCollection;
|
||||
|
||||
const onSubmitNewEmail = useMemoizedFn(async () => {
|
||||
const emailIsValid = isValidEmail(inputValue);
|
||||
if (!emailIsValid) {
|
||||
openErrorMessage('Invalid email address');
|
||||
return;
|
||||
}
|
||||
const options: SelectItem<string>[] = useMemo(() => {
|
||||
return (
|
||||
usersData?.data.map((user) => ({
|
||||
label: (
|
||||
<AvatarUserButton
|
||||
username={user.name}
|
||||
email={user.email}
|
||||
avatarUrl={user.avatarUrl}
|
||||
avatarSize={24}
|
||||
className="cursor-pointer p-0"
|
||||
/>
|
||||
),
|
||||
value: user.email
|
||||
})) || []
|
||||
);
|
||||
}, [usersData]);
|
||||
|
||||
const isAlreadyShared = individualPermissions?.some(
|
||||
(permission) => permission.email === inputValue
|
||||
);
|
||||
const onChangeAccessDropdown = useMemoizedFn((level: ShareRole | null) => {
|
||||
if (level) setDefaultPermissionLevel(level);
|
||||
});
|
||||
|
||||
if (isAlreadyShared) {
|
||||
openErrorMessage('Email already shared');
|
||||
return;
|
||||
}
|
||||
const onSubmitNewEmail = useMemoizedFn(async () => {
|
||||
const emailIsValid = isValidEmail(inputValue);
|
||||
if (!emailIsValid) {
|
||||
openErrorMessage('Invalid email address');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: Parameters<typeof onShareMetric>[0] = {
|
||||
id: assetId,
|
||||
params: [
|
||||
{
|
||||
email: inputValue,
|
||||
role: defaultPermissionLevel
|
||||
}
|
||||
]
|
||||
};
|
||||
const isAlreadyShared = individualPermissions?.some(
|
||||
(permission) => permission.email === inputValue
|
||||
);
|
||||
|
||||
if (assetType === 'metric') {
|
||||
await onShareMetric(payload);
|
||||
} else if (assetType === 'dashboard') {
|
||||
await onShareDashboard(payload);
|
||||
} else if (assetType === 'collection') {
|
||||
await onShareCollection(payload);
|
||||
}
|
||||
if (isAlreadyShared) {
|
||||
openErrorMessage('Email already shared');
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue('');
|
||||
});
|
||||
const payload: Parameters<typeof onShareMetric>[0] = {
|
||||
id: assetId,
|
||||
params: [
|
||||
{
|
||||
email: inputValue,
|
||||
role: defaultPermissionLevel
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center space-x-2">
|
||||
<div className="relative flex w-full items-center">
|
||||
<InputSearchDropdown
|
||||
options={[]}
|
||||
onSelect={() => {}}
|
||||
onSearch={() => {}}
|
||||
onPressEnter={() => {}}
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
placeholder="Invite others by email..."
|
||||
className="w-full"
|
||||
/>
|
||||
if (assetType === 'metric') {
|
||||
await onShareMetric(payload);
|
||||
} else if (assetType === 'dashboard') {
|
||||
await onShareDashboard(payload);
|
||||
} else if (assetType === 'collection') {
|
||||
await onShareCollection(payload);
|
||||
}
|
||||
|
||||
{inputValue && (
|
||||
<AccessDropdown
|
||||
showRemove={false}
|
||||
className="absolute top-[50%] right-[10px] -translate-y-1/2"
|
||||
shareLevel={defaultPermissionLevel}
|
||||
onChangeShareLevel={onChangeAccessDropdown}
|
||||
assetType={assetType}
|
||||
disabled={false}
|
||||
setInputValue('');
|
||||
});
|
||||
|
||||
const onPressEnter = useMemoizedFn((value: string) => {
|
||||
onSubmitNewEmail();
|
||||
});
|
||||
|
||||
const onSelect = useMemoizedFn((value: string) => {
|
||||
const associatedUser = usersData?.data.find((user) => user.email === value);
|
||||
|
||||
if (associatedUser) {
|
||||
setInputValue(associatedUser.email || '');
|
||||
} else {
|
||||
setInputValue(value);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center space-x-2">
|
||||
<div className="relative flex w-full items-center">
|
||||
<InputSearchDropdown
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
onChange={setInputValue}
|
||||
onSearch={setInputValue}
|
||||
onPressEnter={onPressEnter}
|
||||
value={inputValue}
|
||||
placeholder="Invite others by email..."
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{inputValue && (
|
||||
<AccessDropdown
|
||||
showRemove={false}
|
||||
className="absolute top-[50%] right-[10px] -translate-y-1/2"
|
||||
shareLevel={defaultPermissionLevel}
|
||||
onChangeShareLevel={onChangeAccessDropdown}
|
||||
assetType={assetType}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
loading={isInviting}
|
||||
size={'tall'}
|
||||
onClick={onSubmitNewEmail}
|
||||
disabled={disableSubmit}>
|
||||
Invite
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
loading={isInviting}
|
||||
size={'tall'}
|
||||
onClick={onSubmitNewEmail}
|
||||
disabled={disableSubmit}>
|
||||
Invite
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ShareMenuInvite.displayName = 'ShareMenuInvite';
|
||||
|
|
|
@ -42,7 +42,10 @@ export const SidebarUserFooter: React.FC = () => {
|
|||
username={name}
|
||||
email={email}
|
||||
avatarUrl={avatar_url}
|
||||
className={cn(COLLAPSED_HIDDEN, 'w-full')}
|
||||
className={cn(
|
||||
COLLAPSED_HIDDEN,
|
||||
'hover:bg-item-hover active:bg-item-active w-full cursor-pointer'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(COLLAPSED_VISIBLE, 'items-center justify-center')}>
|
||||
|
|
|
@ -6,27 +6,27 @@ import { cn } from '@/lib/classMerge';
|
|||
export const AvatarUserButton = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
{
|
||||
username?: string;
|
||||
username?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
email?: string;
|
||||
email?: string | null;
|
||||
className?: string;
|
||||
avatarSize?: number;
|
||||
}
|
||||
>(({ username, avatarUrl, email, className }, ref) => {
|
||||
>(({ username, avatarUrl, email, className, avatarSize = 28 }, ref) => {
|
||||
const isSameEmailName = email === username;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'hover:bg-item-hover active:bg-item-active flex w-full cursor-pointer items-center gap-x-2 rounded-md p-1',
|
||||
className
|
||||
)}>
|
||||
<Avatar size={28} fallbackClassName="text-base" image={avatarUrl} name={username} />
|
||||
<div ref={ref} className={cn('flex w-full items-center gap-x-2 rounded-md p-1', className)}>
|
||||
<Avatar size={avatarSize} fallbackClassName="text-base" image={avatarUrl} name={username} />
|
||||
<div className="flex w-full flex-col gap-y-0 overflow-hidden">
|
||||
<Text truncate className="flex-grow">
|
||||
{username}
|
||||
</Text>
|
||||
<Text truncate size={'sm'} variant={'secondary'}>
|
||||
{email}
|
||||
</Text>
|
||||
{!isSameEmailName && email && (
|
||||
<Text truncate size={'sm'} variant={'secondary'}>
|
||||
{email}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Select } from '../select/Select';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Select, type SelectItem } from '../select/Select';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface InputSearchDropdownProps {
|
||||
options: {
|
||||
label: string | React.ReactNode;
|
||||
value: string;
|
||||
}[];
|
||||
onSelect: (value: string) => void;
|
||||
export interface InputSearchDropdownProps<T = string> {
|
||||
options: SelectItem<T>[];
|
||||
onSelect: (value: T) => void;
|
||||
placeholder?: string;
|
||||
emptyMessage?: string | false;
|
||||
onSearch: ((value: string) => Promise<void>) | ((value: string) => void);
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
matchPopUpWidth?: boolean;
|
||||
value?: string;
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
onPressEnter: (value: string) => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const InputSearchDropdown = ({
|
||||
export const InputSearchDropdown = <T extends string>({
|
||||
options,
|
||||
onSelect,
|
||||
onPressEnter,
|
||||
|
@ -32,10 +30,9 @@ export const InputSearchDropdown = ({
|
|||
value,
|
||||
className,
|
||||
onChange,
|
||||
loading = false,
|
||||
disabled = false
|
||||
}: InputSearchDropdownProps) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
|
||||
}: InputSearchDropdownProps<T>) => {
|
||||
const handleSearch = useMemo(() => {
|
||||
return {
|
||||
type: 'async' as const,
|
||||
|
@ -45,14 +42,9 @@ export const InputSearchDropdown = ({
|
|||
};
|
||||
}, [onSearch]);
|
||||
|
||||
const handleChange = useMemoizedFn((value: string) => {
|
||||
setInputValue(value);
|
||||
onChange?.(value);
|
||||
});
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={options}
|
||||
items={value.length > 0 ? options : []}
|
||||
placeholder={placeholder}
|
||||
onChange={onSelect}
|
||||
disabled={disabled}
|
||||
|
@ -60,11 +52,14 @@ export const InputSearchDropdown = ({
|
|||
className={className}
|
||||
matchPopUpWidth={matchPopUpWidth}
|
||||
emptyMessage={emptyMessage}
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={handleChange}
|
||||
inputValue={value}
|
||||
onInputValueChange={onChange}
|
||||
search={handleSearch}
|
||||
hideChevron={true}
|
||||
clearOnSelect={false}
|
||||
loading={loading}
|
||||
onPressEnter={onPressEnter}
|
||||
type="input"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -57,6 +57,8 @@ interface BaseSelectProps<T> {
|
|||
hideChevron?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
onPressEnter?: (value: string) => void;
|
||||
type?: 'select' | 'input';
|
||||
clearOnSelect?: boolean;
|
||||
}
|
||||
|
||||
// Clearable version - onChange can return null
|
||||
|
@ -149,6 +151,7 @@ function SelectComponent<T = string>({
|
|||
onChange,
|
||||
placeholder = 'Select an option',
|
||||
emptyMessage = 'No options found.',
|
||||
type = 'select',
|
||||
value,
|
||||
onOpenChange,
|
||||
open: controlledOpen,
|
||||
|
@ -164,6 +167,7 @@ function SelectComponent<T = string>({
|
|||
onInputValueChange,
|
||||
hideChevron = false,
|
||||
onPressEnter,
|
||||
clearOnSelect = true,
|
||||
closeOnSelect = true
|
||||
}: SelectProps<T>) {
|
||||
const [internalInputValue, setInternalInputValue] = React.useState('');
|
||||
|
@ -186,7 +190,7 @@ function SelectComponent<T = string>({
|
|||
if (!newOpen) {
|
||||
// Clear search value after 200ms to avoid flickering
|
||||
setTimeout(() => {
|
||||
setInputValue('');
|
||||
if (clearOnSelect) setInputValue('');
|
||||
setIsFocused(false);
|
||||
}, 125);
|
||||
}
|
||||
|
@ -238,7 +242,7 @@ function SelectComponent<T = string>({
|
|||
if (closeOnSelect) closePopover();
|
||||
onChange?.(item.value);
|
||||
handleOpenChange(false);
|
||||
setInputValue('');
|
||||
if (clearOnSelect) setInputValue('');
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
},
|
||||
|
@ -401,10 +405,11 @@ function SelectComponent<T = string>({
|
|||
readOnly={search === false}
|
||||
className={cn(
|
||||
'flex h-7 w-full items-center justify-between rounded border px-2.5 text-base',
|
||||
'bg-background cursor-pointer transition-all duration-300',
|
||||
'bg-background transition-all duration-300',
|
||||
'focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
disabled ? 'bg-disabled text-gray-light' : '',
|
||||
!selectedItem && !currentInputValue && 'text-text-secondary'
|
||||
!selectedItem && !currentInputValue && 'text-text-secondary',
|
||||
type === 'input' ? 'cursor-text' : 'cursor-pointer'
|
||||
)}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
|
|
|
@ -51,14 +51,18 @@ export const GetUserListRequestSchema = z.object({
|
|||
export type GetUserListRequest = z.infer<typeof GetUserListRequestSchema>;
|
||||
|
||||
export const GetUserToOrganizationRequestSchema = z.object({
|
||||
page: z.coerce.number().min(1).default(1).optional(),
|
||||
page_size: z.coerce.number().min(1).max(5000).default(25).optional(),
|
||||
page: z.coerce.number().min(1).default(1),
|
||||
page_size: z.coerce.number().min(1).max(5000).default(25),
|
||||
user_name: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
//We need this because the frontend sends the roles as a comma-separated string in the query params
|
||||
role: createOptionalQueryArrayPreprocessor(OrganizationRoleSchema).optional(),
|
||||
//We need this because the frontend sends the status as a comma-separated string in the query params
|
||||
status: createOptionalQueryArrayPreprocessor(OrganizationStatusSchema).optional(),
|
||||
status: createOptionalQueryArrayPreprocessor(OrganizationStatusSchema)
|
||||
.default(['active'])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type GetUserToOrganizationRequest = z.infer<typeof GetUserToOrganizationRequestSchema>;
|
||||
export type GetUserToOrganizationRequest = Partial<
|
||||
z.infer<typeof GetUserToOrganizationRequestSchema>
|
||||
>;
|
||||
|
|
|
@ -389,8 +389,8 @@ importers:
|
|||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
framer-motion:
|
||||
specifier: ^12.23.5
|
||||
version: 12.23.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^12.23.6
|
||||
version: 12.23.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
hono:
|
||||
specifier: 'catalog:'
|
||||
version: 4.8.4
|
||||
|
@ -7304,8 +7304,8 @@ packages:
|
|||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
framer-motion@12.23.5:
|
||||
resolution: {integrity: sha512-t+6/f2TUowkr1gVuGwVwxR3ZQupCdCZj0mivG8M8CW2kwHPqtSePomECvmto15qoFCwost77O/XuEsq59MLDKw==}
|
||||
framer-motion@12.23.6:
|
||||
resolution: {integrity: sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
|
@ -8738,11 +8738,11 @@ packages:
|
|||
peerDependencies:
|
||||
monaco-editor: '>=0.36'
|
||||
|
||||
motion-dom@12.23.5:
|
||||
resolution: {integrity: sha512-RrUS4X11w7kIU1COoSVptuYTx1QQ/sViDEI1Yl1zL0nem8UXn3HRcsMrFTCkZSSRLXeVpN540bFP1iS87SicPQ==}
|
||||
motion-dom@12.23.6:
|
||||
resolution: {integrity: sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==}
|
||||
|
||||
motion-utils@12.23.2:
|
||||
resolution: {integrity: sha512-cIEXlBlXAOUyiAtR0S+QPQUM9L3Diz23Bo+zM420NvSd/oPQJwg6U+rT+WRTpp0rizMsBGQOsAwhWIfglUcZfA==}
|
||||
motion-utils@12.23.6:
|
||||
resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
|
@ -19315,10 +19315,10 @@ snapshots:
|
|||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
framer-motion@12.23.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
framer-motion@12.23.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
motion-dom: 12.23.5
|
||||
motion-utils: 12.23.2
|
||||
motion-dom: 12.23.6
|
||||
motion-utils: 12.23.6
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 18.3.1
|
||||
|
@ -20991,11 +20991,11 @@ snapshots:
|
|||
vscode-uri: 3.1.0
|
||||
yaml: 2.8.0
|
||||
|
||||
motion-dom@12.23.5:
|
||||
motion-dom@12.23.6:
|
||||
dependencies:
|
||||
motion-utils: 12.23.2
|
||||
motion-utils: 12.23.6
|
||||
|
||||
motion-utils@12.23.2: {}
|
||||
motion-utils@12.23.6: {}
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
|
|
Loading…
Reference in New Issue