mirror of https://github.com/buster-so/buster.git
popover lineage breadcrumbs
This commit is contained in:
parent
f7bd0abdff
commit
3840d64360
|
@ -100,8 +100,6 @@ export const PolicyCheck: React.FC<{
|
||||||
}
|
}
|
||||||
}, [placement]);
|
}, [placement]);
|
||||||
|
|
||||||
console.log(placement);
|
|
||||||
|
|
||||||
const alignMemo: PopoverProps['align'] = useMemo(() => {
|
const alignMemo: PopoverProps['align'] = useMemo(() => {
|
||||||
switch (placement) {
|
switch (placement) {
|
||||||
case 'top':
|
case 'top':
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { PermissionLineageBreadcrumb } from './PermissionLineageBreadcrumb';
|
||||||
|
import type { DatasetPermissionOverviewUser } from '@/api/asset_interfaces';
|
||||||
|
|
||||||
|
const meta: Meta<typeof PermissionLineageBreadcrumb> = {
|
||||||
|
title: 'Features/Permissions/PermissionLineageBreadcrumb',
|
||||||
|
component: PermissionLineageBreadcrumb,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PermissionLineageBreadcrumb>;
|
||||||
|
|
||||||
|
// Sample data for different scenarios
|
||||||
|
const singleLineage: DatasetPermissionOverviewUser['lineage'] = [
|
||||||
|
[
|
||||||
|
{ type: 'user', name: 'John Doe', id: 'user1' },
|
||||||
|
{ type: 'datasets', name: 'Sales Dataset', id: 'dataset1' },
|
||||||
|
{ type: 'permissionGroups', name: 'Analysts', id: 'group1' }
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const multipleLineage: DatasetPermissionOverviewUser['lineage'] = [
|
||||||
|
[
|
||||||
|
{ type: 'user', name: 'John Doe', id: 'user1' },
|
||||||
|
{ type: 'datasets', name: 'Sales Dataset', id: 'dataset1' }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: 'user', name: 'John Doe', id: 'user1' },
|
||||||
|
{ type: 'datasetGroups', name: 'Marketing Data', id: 'datasetGroup1' }
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
const emptyLineage: DatasetPermissionOverviewUser['lineage'] = [];
|
||||||
|
|
||||||
|
export const SingleLineageCanQuery: Story = {
|
||||||
|
args: {
|
||||||
|
lineage: singleLineage,
|
||||||
|
canQuery: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SingleLineageCannotQuery: Story = {
|
||||||
|
args: {
|
||||||
|
lineage: singleLineage,
|
||||||
|
canQuery: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleLineageCanQuery: Story = {
|
||||||
|
args: {
|
||||||
|
lineage: multipleLineage,
|
||||||
|
canQuery: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleLineageCannotQuery: Story = {
|
||||||
|
args: {
|
||||||
|
lineage: multipleLineage,
|
||||||
|
canQuery: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoLineage: Story = {
|
||||||
|
args: {
|
||||||
|
lineage: emptyLineage,
|
||||||
|
canQuery: false
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,10 +1,12 @@
|
||||||
import type { DatasetPermissionOverviewUser } from '@/api/asset_interfaces';
|
import type { DatasetPermissionOverviewUser } from '@/api/asset_interfaces';
|
||||||
import { AppMaterialIcons, AppPopover } from '@/components/ui';
|
import { ChevronRight } from '@/components/ui/icons';
|
||||||
|
import { Popover } from '@/components/ui/tooltip/Popover';
|
||||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { createStyles } from 'antd-style';
|
|
||||||
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 { cva } from 'class-variance-authority';
|
||||||
|
|
||||||
export const PermissionLineageBreadcrumb: React.FC<{
|
export const PermissionLineageBreadcrumb: React.FC<{
|
||||||
lineage: DatasetPermissionOverviewUser['lineage'];
|
lineage: DatasetPermissionOverviewUser['lineage'];
|
||||||
|
@ -56,10 +58,10 @@ const MultipleLineage: React.FC<{
|
||||||
lineage: DatasetPermissionOverviewUser['lineage'];
|
lineage: DatasetPermissionOverviewUser['lineage'];
|
||||||
canQuery: DatasetPermissionOverviewUser['can_query'];
|
canQuery: DatasetPermissionOverviewUser['can_query'];
|
||||||
}> = ({ lineage, canQuery }) => {
|
}> = ({ lineage, canQuery }) => {
|
||||||
const { styles, cx } = useStyles();
|
// const { styles, cx } = useStyles();
|
||||||
const Content = useMemo(() => {
|
const Content = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-[200px] flex-col space-y-2 p-2">
|
<div className="flex min-w-[200px] flex-col space-y-2">
|
||||||
{lineage.map((item, lineageindex) => {
|
{lineage.map((item, lineageindex) => {
|
||||||
const items = item.map((v, index) => {
|
const items = item.map((v, index) => {
|
||||||
return <SelectedComponent key={index} item={v} />;
|
return <SelectedComponent key={index} item={v} />;
|
||||||
|
@ -71,17 +73,10 @@ const MultipleLineage: React.FC<{
|
||||||
);
|
);
|
||||||
}, [lineage]);
|
}, [lineage]);
|
||||||
|
|
||||||
const onClickPreflight = useMemoizedFn((e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppPopover placement="topRight" destroyTooltipOnHide trigger="click" content={Content}>
|
<Popover side="top" align="start" trigger="click" content={Content} size="sm">
|
||||||
<div className={cx(styles.linearItem, 'clickable')} onClick={onClickPreflight}>
|
<div className={linearItem({ clickable: true })}>Multiple access sources</div>
|
||||||
Multiple access sources
|
</Popover>
|
||||||
</div>
|
|
||||||
</AppPopover>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,39 +86,30 @@ interface LineageItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
const UserLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
||||||
const { styles, cx } = useStyles();
|
return <div className={linearItem({ clickable: false })}>{name}</div>;
|
||||||
return <div className={cx(styles.linearItem)}>{name}</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DatasetLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
const DatasetLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
||||||
const { styles, cx } = useStyles();
|
|
||||||
return (
|
return (
|
||||||
<Link href={createBusterRoute({ route: BusterRoutes.APP_DATASETS_ID, datasetId: id })}>
|
<Link href={createBusterRoute({ route: BusterRoutes.APP_DATASETS_ID, datasetId: id })}>
|
||||||
<div className={cx(styles.linearItem, 'clickable')}>{name}</div>
|
<div className={linearItem({ clickable: true })}>{name}</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PermissionGroupLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
const PermissionGroupLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
||||||
const { styles, cx } = useStyles();
|
return <div className={linearItem({ clickable: false })}>{name}</div>;
|
||||||
return <div className={cx(styles.linearItem)}>{name}</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DatasetGroupLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
const DatasetGroupLineageItem: React.FC<LineageItemProps> = ({ name, id }) => {
|
||||||
const { styles, cx } = useStyles();
|
return <div className={linearItem({ clickable: false })}>{name}</div>;
|
||||||
return <div className={cx(styles.linearItem)}>{name}</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CanQueryTag: React.FC<{
|
const CanQueryTag: React.FC<{
|
||||||
canQuery: boolean;
|
canQuery: boolean;
|
||||||
}> = ({ canQuery }) => {
|
}> = ({ canQuery }) => {
|
||||||
const { styles, cx } = useStyles();
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={canQueryTag({ status: canQuery ? 'success' : 'error' })}>
|
||||||
className={cx(
|
|
||||||
styles.canQueryTag,
|
|
||||||
canQuery ? styles.canQueryTagSuccess : styles.canQueryTagError
|
|
||||||
)}>
|
|
||||||
{canQuery ? 'Can query' : 'Cannot query'}
|
{canQuery ? 'Can query' : 'Cannot query'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -133,13 +119,12 @@ const LineageBreadcrumb: React.FC<{
|
||||||
items: React.ReactNode[];
|
items: React.ReactNode[];
|
||||||
canQuery: boolean;
|
canQuery: boolean;
|
||||||
}> = ({ items, canQuery }) => {
|
}> = ({ items, canQuery }) => {
|
||||||
const { styles, cx } = useStyles();
|
const BreadcrumbIcon = <ChevronRight />;
|
||||||
const BreadcrumbIcon = <AppMaterialIcons icon="chevron_right" />;
|
|
||||||
|
|
||||||
const allItems = [...items, <CanQueryTag key="can-query" canQuery={canQuery} />];
|
const allItems = [...items, <CanQueryTag key="can-query" canQuery={canQuery} />];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.linearContainer, 'flex justify-end space-x-0')}>
|
<div className={cn('text-text-secondary', 'flex justify-end space-x-0')}>
|
||||||
{allItems.map((item, index) => {
|
{allItems.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex items-center space-x-0">
|
<div key={index} className="flex items-center space-x-0">
|
||||||
|
@ -152,33 +137,36 @@ const LineageBreadcrumb: React.FC<{
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = createStyles(({ token, css }) => ({
|
const linearItem = cva('text-text-secondary text-base px-1 py-1.5 rounded-sm', {
|
||||||
linearContainer: css`
|
variants: {
|
||||||
color: ${token.colorTextSecondary};
|
clickable: {
|
||||||
`,
|
true: 'cursor-pointer hover:text-text hover:bg-item-hover-active',
|
||||||
linearItem: css`
|
false: ''
|
||||||
color: ${token.colorTextSecondary};
|
|
||||||
padding: 4px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
color: ${token.colorText};
|
|
||||||
background-color: ${token.colorFillSecondary};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
}
|
||||||
canQueryTag: css`
|
});
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 6px;
|
const canQueryTag = cva('rounded-sm px-1 py-1.5 text-base', {
|
||||||
`,
|
variants: {
|
||||||
canQueryTagSuccess: css`
|
status: {
|
||||||
color: #34a32d;
|
success: 'bg-success-background text-success-foreground',
|
||||||
background-color: #edfff0;
|
error: 'bg-danger-background text-danger-foreground'
|
||||||
`,
|
}
|
||||||
canQueryTagError: css`
|
}
|
||||||
color: #ff9e00;
|
});
|
||||||
background-color: #fff7ed;
|
|
||||||
`
|
// const useStyles = createStyles(({ token, css }) => ({
|
||||||
}));
|
|
||||||
|
// canQueryTag: css`
|
||||||
|
// border-radius: 4px;
|
||||||
|
// padding: 4px 6px;
|
||||||
|
// `,
|
||||||
|
// canQueryTagSuccess: css`
|
||||||
|
// color: #34a32d;
|
||||||
|
// background-color: #edfff0;
|
||||||
|
// `,
|
||||||
|
// canQueryTagError: css`
|
||||||
|
// color: #ff9e00;
|
||||||
|
// background-color: #fff7ed;
|
||||||
|
// `
|
||||||
|
// }));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
PopoverRoot as PopoverBase,
|
PopoverRoot as PopoverBase,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
PopoverContentVariant,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
PopoverTriggerType
|
PopoverTriggerType
|
||||||
} from './PopoverBase';
|
} from './PopoverBase';
|
||||||
|
@ -14,7 +15,8 @@ export interface PopoverProps
|
||||||
content: React.ReactNode;
|
content: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
headerContent?: string | React.ReactNode;
|
headerContent?: string | React.ReactNode;
|
||||||
triggerType?: PopoverTriggerType;
|
trigger?: PopoverTriggerType;
|
||||||
|
size?: PopoverContentVariant['size'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Popover = React.memo<PopoverProps>(
|
export const Popover = React.memo<PopoverProps>(
|
||||||
|
@ -25,16 +27,18 @@ export const Popover = React.memo<PopoverProps>(
|
||||||
side,
|
side,
|
||||||
className = '',
|
className = '',
|
||||||
headerContent,
|
headerContent,
|
||||||
triggerType = 'click',
|
trigger = 'click',
|
||||||
|
size = 'default',
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<PopoverBase triggerType={triggerType} {...props}>
|
<PopoverBase trigger={trigger} {...props}>
|
||||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
align={align}
|
align={align}
|
||||||
side={side}
|
side={side}
|
||||||
className={className}
|
className={className}
|
||||||
|
size={size}
|
||||||
headerContent={headerContent && <PopoverHeaderContent title={headerContent} />}>
|
headerContent={headerContent && <PopoverHeaderContent title={headerContent} />}>
|
||||||
{content}
|
{content}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|
|
@ -12,26 +12,26 @@ export type PopoverTriggerType = 'click' | 'hover';
|
||||||
const Popover = PopoverPrimitive.Root;
|
const Popover = PopoverPrimitive.Root;
|
||||||
|
|
||||||
interface PopoverProps extends React.ComponentPropsWithoutRef<typeof Popover> {
|
interface PopoverProps extends React.ComponentPropsWithoutRef<typeof Popover> {
|
||||||
triggerType?: PopoverTriggerType;
|
trigger?: PopoverTriggerType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopoverRoot: React.FC<PopoverProps> = ({ children, triggerType = 'click', ...props }) => {
|
const PopoverRoot: React.FC<PopoverProps> = ({ children, trigger = 'click', ...props }) => {
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
const handleMouseEnter = useMemoizedFn(() => {
|
const handleMouseEnter = useMemoizedFn(() => {
|
||||||
if (triggerType === 'hover') {
|
if (trigger === 'hover') {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleMouseLeave = useMemoizedFn(() => {
|
const handleMouseLeave = useMemoizedFn(() => {
|
||||||
if (triggerType === 'hover') {
|
if (trigger === 'hover') {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const content =
|
const content =
|
||||||
triggerType === 'hover' ? (
|
trigger === 'hover' ? (
|
||||||
<div className="relative" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
<div className="relative" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||||
<div className="absolute -inset-[4px]" />
|
<div className="absolute -inset-[4px]" />
|
||||||
<div className="relative z-10">{children}</div>
|
<div className="relative z-10">{children}</div>
|
||||||
|
@ -41,7 +41,7 @@ const PopoverRoot: React.FC<PopoverProps> = ({ children, triggerType = 'click',
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover {...props} open={triggerType === 'hover' ? isOpen : undefined}>
|
<Popover {...props} open={trigger === 'hover' ? isOpen : undefined}>
|
||||||
{content}
|
{content}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
@ -64,11 +64,13 @@ const popoverContentVariant = cva('', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type PopoverContentVariant = VariantProps<typeof popoverContentVariant>;
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
|
||||||
headerContent?: React.ReactNode;
|
headerContent?: React.ReactNode;
|
||||||
} & VariantProps<typeof popoverContentVariant>
|
} & PopoverContentVariant
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
{ className, align = 'center', children, sideOffset = 4, headerContent, size, ...props },
|
{ className, align = 'center', children, sideOffset = 4, headerContent, size, ...props },
|
||||||
|
|
Loading…
Reference in New Issue