share menu popover

This commit is contained in:
Nate Kelley 2025-03-03 21:36:12 -07:00
parent 3840d64360
commit 74b832a9df
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
9 changed files with 90 additions and 20 deletions

View File

@ -2,7 +2,6 @@ import type { DatasetPermissionOverviewUser } from '@/api/asset_interfaces';
import { ChevronRight } from '@/components/ui/icons'; import { ChevronRight } from '@/components/ui/icons';
import { Popover } from '@/components/ui/tooltip/Popover'; import { Popover } from '@/components/ui/tooltip/Popover';
import { BusterRoutes, createBusterRoute } from '@/routes'; import { BusterRoutes, createBusterRoute } from '@/routes';
import { useMemoizedFn } from 'ahooks';
import Link from 'next/link'; import Link from 'next/link';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';

View File

@ -0,0 +1,71 @@
import type { Meta, StoryObj } from '@storybook/react';
import { ShareMenu } from './ShareMenu';
import { BusterShare, ShareAssetType, ShareRole } from '@/api/asset_interfaces';
import { Button } from 'antd';
const meta: Meta<typeof ShareMenu> = {
title: 'Features/Share/ShareMenu',
component: ShareMenu,
parameters: {
layout: 'centered'
},
decorators: [
(Story) => (
<div
style={{
width: '400px',
height: '400px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}>
<Story />
</div>
)
]
};
export default meta;
type Story = StoryObj<typeof ShareMenu>;
const mockShareConfig: BusterShare = {
sharingKey: 'mock-sharing-key',
individual_permissions: null,
team_permissions: null,
organization_permissions: null,
password_secret_id: null,
public_expiry_date: null,
public_enabled_by: null,
publicly_accessible: false,
public_password: null,
permission: ShareRole.OWNER
};
export const Default: Story = {
args: {
shareAssetConfig: mockShareConfig,
assetId: '123',
assetType: ShareAssetType.DASHBOARD,
children: <Button>Share</Button>
}
};
export const ViewerPermission: Story = {
args: {
...Default.args,
shareAssetConfig: {
...mockShareConfig,
permission: ShareRole.VIEWER
}
}
};
export const PublicAsset: Story = {
args: {
...Default.args,
shareAssetConfig: {
...mockShareConfig,
publicly_accessible: true
}
}
};

View File

@ -1,8 +1,7 @@
'use client'; 'use client';
import React, { PropsWithChildren } from 'react'; import React, { PropsWithChildren } from 'react';
import { PopoverProps } from 'antd'; import { Popover } from '@/components/ui/tooltip/Popover';
import { AppPopover } from '@/components/ui/tooltip/AppPopover';
import { AppTooltip } from '@/components/ui/tooltip'; import { AppTooltip } from '@/components/ui/tooltip';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { BusterShare, ShareAssetType } from '@/api/asset_interfaces'; import { BusterShare, ShareAssetType } from '@/api/asset_interfaces';
@ -11,12 +10,11 @@ import { isShareMenuVisible } from './publicHelpers';
export const ShareMenu: React.FC< export const ShareMenu: React.FC<
PropsWithChildren<{ PropsWithChildren<{
placement?: PopoverProps['placement'];
shareAssetConfig: BusterShare | null; shareAssetConfig: BusterShare | null;
assetId: string; assetId: string;
assetType: ShareAssetType; assetType: ShareAssetType;
}> }>
> = ({ children, shareAssetConfig, assetId, assetType, placement = 'bottomLeft' }) => { > = ({ children, shareAssetConfig, assetId, assetType }) => {
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const onOpenChange = useMemoizedFn((v: boolean) => { const onOpenChange = useMemoizedFn((v: boolean) => {
@ -32,10 +30,8 @@ export const ShareMenu: React.FC<
const permission = shareAssetConfig?.permission; const permission = shareAssetConfig?.permission;
return ( return (
<AppPopover <Popover
trigger={['click']} size={'none'}
destroyTooltipOnHide
placement={placement}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
content={ content={
shareAssetConfig ? ( shareAssetConfig ? (
@ -50,6 +46,6 @@ export const ShareMenu: React.FC<
<AppTooltip title={!isOpen ? 'Share item' : ''}> <AppTooltip title={!isOpen ? 'Share item' : ''}>
<div className="flex">{children}</div> <div className="flex">{children}</div>
</AppTooltip> </AppTooltip>
</AppPopover> </Popover>
); );
}; };

View File

@ -1,6 +1,6 @@
import { BusterShare, ShareRole, ShareAssetType } from '@/api/asset_interfaces'; import { type BusterShare, ShareRole, ShareAssetType } from '@/api/asset_interfaces';
import { useBusterNotifications } from '@/context/BusterNotifications'; import { useBusterNotifications } from '@/context/BusterNotifications';
import React from 'react'; import React, { useState } from 'react';
import { ShareMenuTopBar, ShareMenuTopBarOptions } from './ShareMenuTopBar'; import { ShareMenuTopBar, ShareMenuTopBarOptions } from './ShareMenuTopBar';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes'; import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
@ -15,7 +15,7 @@ export const ShareMenuContent: React.FC<{
}> = React.memo(({ assetId, assetType, shareAssetConfig, permission }) => { }> = React.memo(({ assetId, assetType, shareAssetConfig, permission }) => {
const { openSuccessMessage } = useBusterNotifications(); const { openSuccessMessage } = useBusterNotifications();
const isOwner = permission === ShareRole.OWNER; const isOwner = permission === ShareRole.OWNER;
const [selectedOptions, setSelectedOptions] = React.useState<ShareMenuTopBarOptions>( const [selectedOptions, setSelectedOptions] = useState<ShareMenuTopBarOptions>(
isOwner ? ShareMenuTopBarOptions.Share : ShareMenuTopBarOptions.Embed isOwner ? ShareMenuTopBarOptions.Share : ShareMenuTopBarOptions.Embed
); );
const previousSelection = React.useRef<ShareMenuTopBarOptions>(selectedOptions); const previousSelection = React.useRef<ShareMenuTopBarOptions>(selectedOptions);

View File

@ -87,7 +87,7 @@ const ShareMenuContentShare: React.FC<{
ShareRole.VIEWER ShareRole.VIEWER
); );
const disableSubmit = !inputHasText(inputValue) || !validate(inputValue); const disableSubmit = !inputHasText(inputValue) || !validate(inputValue);
const hasUserTeams = userTeams.length > 0; const hasUserTeams = userTeams?.length > 0;
const onSubmitNewEmail = useMemoizedFn(async () => { const onSubmitNewEmail = useMemoizedFn(async () => {
const isValidEmail = validate(inputValue); const isValidEmail = validate(inputValue);

View File

@ -56,7 +56,7 @@ export const StatusDropdownContent: React.FC<{
onSelect={onSelect} onSelect={onSelect}
selectType="single" selectType="single"
menuHeader="Verification status..."> menuHeader="Verification status...">
<span className="inline-block">{children}</span> {children}
</Dropdown> </Dropdown>
); );
}); });

View File

@ -71,8 +71,8 @@ const DropdownMenuContent = React.forwardRef<
footerContent?: React.ReactNode; footerContent?: React.ReactNode;
} }
>(({ className, children, sideOffset = 4, footerContent, ...props }, ref) => { >(({ className, children, sideOffset = 4, footerContent, ...props }, ref) => {
const NodeWrapper = footerContent ? 'div' : React.Fragment; const NodeWrapper = footerContent ? 'div' : 'span';
const nodeWrapperProps = footerContent ? { className: 'p-2' } : {}; const nodeWrapperProps = footerContent ? { className: 'p-2' } : { className: 'inline-block' };
return ( return (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>

View File

@ -33,14 +33,16 @@ export const Popover = React.memo<PopoverProps>(
}) => { }) => {
return ( return (
<PopoverBase trigger={trigger} {...props}> <PopoverBase trigger={trigger} {...props}>
<PopoverTrigger asChild>{children}</PopoverTrigger> <PopoverTrigger asChild>
<span className="inline-block">{children}</span>
</PopoverTrigger>
<PopoverContent <PopoverContent
align={align} align={align}
side={side} side={side}
className={className} className={className}
size={size} size={size}
headerContent={headerContent && <PopoverHeaderContent title={headerContent} />}> headerContent={headerContent && <PopoverHeaderContent title={headerContent} />}>
{content} <>{content}</>
</PopoverContent> </PopoverContent>
</PopoverBase> </PopoverBase>
); );

View File

@ -88,7 +88,9 @@ const PopoverContent = React.forwardRef<
)} )}
{...props}> {...props}>
{headerContent && <>{headerContent}</>} {headerContent && <>{headerContent}</>}
<div className={cn(popoverContentVariant({ size }), className)}>{children}</div> <div className={cn(popoverContentVariant({ size }), className)}>
<>{children}</>
</div>
</PopoverPrimitive.Content> </PopoverPrimitive.Content>
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
) )