mirror of https://github.com/buster-so/buster.git
rip out app select component
This commit is contained in:
parent
f364556f90
commit
d3081a6e57
|
@ -2,7 +2,8 @@ import { ShareAssetType, VerificationStatus, BusterChatListItem } from '@/api/as
|
|||
import { makeHumanReadble, formatDate } from '@/lib';
|
||||
import React, { memo, useMemo, useRef, useState } from 'react';
|
||||
import { StatusBadgeIndicator, getShareStatus } from '../../../../components/features/Lists';
|
||||
import { BusterUserAvatar, Text } from '@/components/ui';
|
||||
import { Text } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { BusterListColumn, BusterListRow } from '@/components/ui/list';
|
||||
|
@ -180,7 +181,7 @@ TitleCell.displayName = 'TitleCell';
|
|||
|
||||
const OwnerCell = memo<{ name: string; image: string | undefined }>(({ name, image }) => (
|
||||
<div className="flex pl-0">
|
||||
<BusterUserAvatar image={image} name={name} size={18} />
|
||||
<Avatar image={image} name={name} className="h-[18px] w-[18px]" />
|
||||
</div>
|
||||
));
|
||||
OwnerCell.displayName = 'OwnerCell';
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { BusterUserAvatar, Text, Title, AppMaterialIcons } from '@/components/ui';
|
||||
import { Text, Title, AppMaterialIcons } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import type { OrganizationUser } from '@/api/asset_interfaces';
|
||||
import { Button } from 'antd';
|
||||
|
||||
|
@ -17,7 +18,7 @@ UserHeader.displayName = 'UserHeader';
|
|||
const UserInfo: React.FC<{ user: OrganizationUser }> = ({ user }) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<BusterUserAvatar size={48} name={user.name} />
|
||||
<Avatar className="h-[48px] w-[48px]" name={user.name} />
|
||||
<div className="flex flex-col">
|
||||
<Title level={4}>{user.name}</Title>
|
||||
<Text size="sm" type="secondary">
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useUserConfigContextSelector } from '@/context/Users';
|
|||
import { createStyles } from 'antd-style';
|
||||
import { formatDate } from '@/lib/date';
|
||||
import { Text, Title } from '@/components/ui';
|
||||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { Card } from 'antd';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
|
@ -41,7 +41,7 @@ export default function ProfilePage() {
|
|||
body: 'flex flex-col space-y-3'
|
||||
}}>
|
||||
<div className={'flex items-center space-x-2.5'}>
|
||||
<BusterUserAvatar name={name} size={48} />
|
||||
<Avatar name={name} className="h-[48px] w-[48px]" />
|
||||
<Title level={4}>{name}</Title>
|
||||
</div>
|
||||
<div className={'flex flex-col space-y-0.5'}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AppDropdownSelect } from '@/components/ui/dropdown';
|
||||
import { AppMaterialIcons } from '@/components/ui';
|
||||
import { Dropdown, type DropdownItem, type DropdownProps } from '@/components/ui/dropdown';
|
||||
import { AppTooltip } from '@/components/ui/tooltip';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { useBusterCollectionListContextSelector } from '@/context/Collections';
|
||||
|
@ -21,12 +21,13 @@ export const SaveToCollectionsDropdown: React.FC<{
|
|||
const [openCollectionModal, setOpenCollectionModal] = React.useState(false);
|
||||
const [showDropdown, setShowDropdown] = React.useState(false);
|
||||
|
||||
const items = useMemo(
|
||||
const items: DropdownProps['items'] = useMemo(
|
||||
() =>
|
||||
(collectionsList || []).map((collection) => {
|
||||
(collectionsList || []).map<DropdownItem>((collection) => {
|
||||
return {
|
||||
key: collection.id,
|
||||
value: collection.id,
|
||||
label: collection.name,
|
||||
selected: selectedCollections.some((id) => id === collection.id),
|
||||
onClick: () => onClickItem(collection),
|
||||
link: createBusterRoute({
|
||||
route: BusterRoutes.APP_COLLECTIONS_ID,
|
||||
|
@ -34,7 +35,7 @@ export const SaveToCollectionsDropdown: React.FC<{
|
|||
})
|
||||
};
|
||||
}),
|
||||
[collectionsList]
|
||||
[collectionsList, selectedCollections]
|
||||
);
|
||||
|
||||
const onClickItem = useMemoizedFn((collection: BusterCollectionListItem) => {
|
||||
|
@ -84,22 +85,16 @@ export const SaveToCollectionsDropdown: React.FC<{
|
|||
|
||||
return (
|
||||
<>
|
||||
<AppDropdownSelect
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
className="flex! h-fit! items-center"
|
||||
headerContent={'Save to a collection'}
|
||||
<Dropdown
|
||||
side="bottom"
|
||||
align="start"
|
||||
menuHeader={'Save to a collection'}
|
||||
open={showDropdown}
|
||||
onOpenChange={onOpenChange}
|
||||
footerContent={memoizedButton}
|
||||
items={items}
|
||||
selectedItems={selectedCollections}>
|
||||
{showDropdown ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<AppTooltip title={showDropdown ? '' : 'Save to collection'}>{children} </AppTooltip>
|
||||
)}
|
||||
</AppDropdownSelect>
|
||||
items={items}>
|
||||
<AppTooltip title={showDropdown ? '' : 'Save to collection'}>{children} </AppTooltip>
|
||||
</Dropdown>
|
||||
|
||||
<NewCollectionModal
|
||||
open={openCollectionModal}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useMemoizedFn } from 'ahooks';
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes/busterRoutes';
|
||||
import { Button } from 'antd';
|
||||
import { AppDropdownSelect, AppDropdownSelectProps } from '@/components/ui/dropdown';
|
||||
import { Dropdown, type DropdownProps } from '@/components/ui/dropdown';
|
||||
import { AppTooltip } from '@/components/ui/tooltip';
|
||||
import { AppMaterialIcons } from '@/components/ui';
|
||||
import type { BusterMetric, BusterDashboardListItem } from '@/api/asset_interfaces';
|
||||
|
@ -35,12 +35,13 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
}
|
||||
});
|
||||
|
||||
const items = useMemo(
|
||||
const items: DropdownProps['items'] = useMemo(
|
||||
() =>
|
||||
(dashboardsList || [])?.map((dashboard) => {
|
||||
return {
|
||||
key: dashboard.id,
|
||||
value: dashboard.id,
|
||||
label: dashboard.name || 'New dashboard',
|
||||
selected: selectedDashboards.some((d) => d.id === dashboard.id),
|
||||
onClick: () => onClickItem(dashboard),
|
||||
link: createBusterRoute({
|
||||
route: BusterRoutes.APP_DASHBOARD_ID,
|
||||
|
@ -51,10 +52,6 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
[dashboardsList]
|
||||
);
|
||||
|
||||
const selectedItems = useMemo(() => {
|
||||
return selectedDashboards.map((d) => d.id);
|
||||
}, [selectedDashboards]);
|
||||
|
||||
const onClickNewDashboardButton = useMemoizedFn(async () => {
|
||||
const res = await onCreateNewDashboard({
|
||||
rerouteToDashboard: false
|
||||
|
@ -82,8 +79,6 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
setShowDropdown(open);
|
||||
});
|
||||
|
||||
const memoizedTrigger = useMemo<AppDropdownSelectProps['trigger']>(() => ['click'], []);
|
||||
|
||||
const memoizedButton = useMemo(() => {
|
||||
return (
|
||||
<Button
|
||||
|
@ -99,18 +94,15 @@ export const SaveToDashboardDropdown: React.FC<{
|
|||
}, [isCreatingDashboard, onClickNewDashboardButton]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppDropdownSelect
|
||||
trigger={memoizedTrigger}
|
||||
headerContent={'Save to a dashboard'}
|
||||
placement="bottomRight"
|
||||
open={showDropdown}
|
||||
onOpenChange={onOpenChange}
|
||||
footerContent={memoizedButton}
|
||||
items={items}
|
||||
selectedItems={selectedItems}>
|
||||
<AppTooltip title={showDropdown ? '' : 'Save to dashboard'}>{children}</AppTooltip>
|
||||
</AppDropdownSelect>
|
||||
</>
|
||||
<Dropdown
|
||||
side="bottom"
|
||||
align="start"
|
||||
menuHeader={'Save to a dashboard'}
|
||||
open={showDropdown}
|
||||
onOpenChange={onOpenChange}
|
||||
footerContent={memoizedButton}
|
||||
items={items}>
|
||||
<AppTooltip title={showDropdown ? '' : 'Save to collection'}>{children} </AppTooltip>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Text, BusterUserAvatar } from '@/components/ui';
|
||||
import { Text } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import React from 'react';
|
||||
|
||||
export const ListUserItem = React.memo(({ name, email }: { name: string; email: string }) => {
|
||||
return (
|
||||
<div className="flex w-full items-center space-x-1.5">
|
||||
<div className="flex items-center space-x-2">
|
||||
<BusterUserAvatar size={18} name={name} />
|
||||
<Avatar className="h-[18px] w-[18px]" name={name} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-0">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { AccessDropdown } from './AccessDropdown';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -24,7 +24,7 @@ export const IndividualSharePerson: React.FC<{
|
|||
<div className="flex items-center justify-between space-x-2 px-0 py-1">
|
||||
<div className="flex h-full items-center space-x-2">
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<BusterUserAvatar size={24} name={name || email} />
|
||||
<Avatar className="h-[24px] w-[24px]" name={name || email} />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-0">
|
||||
<Text className="">{name || email}</Text>
|
||||
|
|
|
@ -5,21 +5,27 @@ import { Tooltip } from '../tooltip/Tooltip';
|
|||
import { BusterLogo } from '@/assets/svg/BusterLogo';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface BusterUserAvatarProps {
|
||||
export interface AvatarProps {
|
||||
image?: string;
|
||||
name?: string | null;
|
||||
className?: string;
|
||||
useToolTip?: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<BusterUserAvatarProps> = React.memo(
|
||||
({ image, name, className, useToolTip }) => {
|
||||
export const Avatar: React.FC<AvatarProps> = React.memo(
|
||||
({ image, name, className, useToolTip, size }) => {
|
||||
const hasName = !!name;
|
||||
const nameLetters = hasName ? createNameLetters(name, image) : '';
|
||||
|
||||
return (
|
||||
<Tooltip title={useToolTip ? nameLetters : ''}>
|
||||
<AvatarBase className={className}>
|
||||
<AvatarBase
|
||||
className={className}
|
||||
style={{
|
||||
width: size,
|
||||
height: size
|
||||
}}>
|
||||
{image && <AvatarImage src={image} />}
|
||||
<AvatarFallback className={cn(!hasName && 'border bg-white')}>
|
||||
{nameLetters || <BusterAvatarFallback />}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './Avatar';
|
|
@ -1,4 +1,3 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
import { AppCodeEditor } from '../inputs/AppCodeEditor';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
|
@ -29,8 +28,6 @@ export const CodeCard: React.FC<{
|
|||
onChange,
|
||||
readOnly = false
|
||||
}) => {
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const ShownButtons = buttons === true ? <CardButtons fileName={fileName} code={code} /> : buttons;
|
||||
|
||||
return (
|
||||
|
@ -42,7 +39,7 @@ export const CodeCard: React.FC<{
|
|||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className={cn('bg-background overflow-hidden p-0', bodyClassName)}>
|
||||
<div className={cx(styles.containerBody, bodyClassName)}>
|
||||
<div className={cn('bg-background', bodyClassName)}>
|
||||
<AppCodeEditor
|
||||
language={language}
|
||||
value={code}
|
||||
|
@ -102,18 +99,3 @@ const CardButtons: React.FC<{
|
|||
);
|
||||
});
|
||||
CardButtons.displayName = 'CardButtons';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
container: css`
|
||||
border-radius: ${token.borderRadius}px;
|
||||
border: 0.5px solid ${token.colorBorder};
|
||||
`,
|
||||
containerHeader: css`
|
||||
background: ${token.controlItemBgActive};
|
||||
border-bottom: 0.5px solid ${token.colorBorder};
|
||||
height: 32px;
|
||||
`,
|
||||
containerBody: css`
|
||||
background: ${token.colorBgBase};
|
||||
`
|
||||
}));
|
||||
|
|
|
@ -302,7 +302,7 @@ export const WithSelectionMultiple: Story = {
|
|||
<Dropdown
|
||||
selectType="multiple"
|
||||
items={items}
|
||||
menuHeader={{ placeholder: 'Search items...' }}
|
||||
menuHeader={'Search items...'}
|
||||
onSelect={handleSelect}
|
||||
children={<Button>Selection Menu</Button>}
|
||||
/>
|
||||
|
@ -345,9 +345,7 @@ export const WithSecondaryLabel: Story = {
|
|||
// Example with search header
|
||||
export const WithSearchHeader: Story = {
|
||||
args: {
|
||||
menuHeader: {
|
||||
placeholder: 'Search items...'
|
||||
},
|
||||
menuHeader: 'Search items...',
|
||||
items: [
|
||||
{
|
||||
value: '1',
|
||||
|
@ -393,9 +391,7 @@ export const WithSearchHeader: Story = {
|
|||
// Example with long text to test truncation
|
||||
export const WithLongText: Story = {
|
||||
args: {
|
||||
menuHeader: {
|
||||
placeholder: 'Search items...'
|
||||
},
|
||||
menuHeader: 'Search items...',
|
||||
items: [
|
||||
...Array.from({ length: 100 }).map(() => {
|
||||
const label = faker.commerce.product();
|
||||
|
@ -508,10 +504,38 @@ export const WithLinksAndMultipleSelection: Story = {
|
|||
open
|
||||
selectType="multiple"
|
||||
items={items}
|
||||
menuHeader={{ placeholder: 'Search documentation...' }}
|
||||
menuHeader="Search documentation..."
|
||||
onSelect={handleSelect}
|
||||
children={<Button>Documentation Sections</Button>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const WithFooterContent: Story = {
|
||||
args: {
|
||||
items: [
|
||||
{
|
||||
value: '1',
|
||||
label: 'Option 1',
|
||||
onClick: () => alert('Option 1 clicked')
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: 'Option 2',
|
||||
onClick: () => alert('Option 2 clicked')
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: 'Option 3',
|
||||
onClick: () => alert('Option 3 clicked')
|
||||
}
|
||||
],
|
||||
footerContent: (
|
||||
<Button variant={'black'} block>
|
||||
Footer Content
|
||||
</Button>
|
||||
),
|
||||
children: <Button>Menu with Footer Content</Button>
|
||||
}
|
||||
};
|
||||
|
|
|
@ -48,9 +48,9 @@ export interface DropdownDivider {
|
|||
export type DropdownItems = (DropdownItem | DropdownDivider | React.ReactNode)[];
|
||||
|
||||
export interface DropdownProps extends DropdownMenuProps {
|
||||
items?: DropdownItems;
|
||||
items: DropdownItems;
|
||||
selectType?: 'single' | 'multiple' | 'none';
|
||||
menuHeader?: string | React.ReactNode | { placeholder: string };
|
||||
menuHeader?: string | React.ReactNode; //if string it will render a search box
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
closeOnSelect?: boolean;
|
||||
|
@ -59,6 +59,7 @@ export interface DropdownProps extends DropdownMenuProps {
|
|||
side?: 'top' | 'right' | 'bottom' | 'left';
|
||||
emptyStateText?: string;
|
||||
className?: string;
|
||||
footerContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
const dropdownItemKey = (item: DropdownItems[number], index: number) => {
|
||||
|
@ -84,6 +85,7 @@ export const Dropdown: React.FC<DropdownProps> = React.memo(
|
|||
onOpenChange,
|
||||
emptyStateText = 'No items found',
|
||||
className,
|
||||
footerContent,
|
||||
...props
|
||||
}) => {
|
||||
const { filteredItems, searchText, handleSearchChange } = useDebounceSearch({
|
||||
|
@ -133,7 +135,8 @@ export const Dropdown: React.FC<DropdownProps> = React.memo(
|
|||
<DropdownMenuContent
|
||||
className={cn('max-w-72 min-w-44', className)}
|
||||
align={align}
|
||||
side={side}>
|
||||
side={side}
|
||||
footerContent={footerContent}>
|
||||
{menuHeader && (
|
||||
<>
|
||||
<DropdownMenuHeaderSelector
|
||||
|
@ -357,17 +360,11 @@ const DropdownMenuHeaderSelector: React.FC<{
|
|||
onChange: (text: string) => void;
|
||||
text: string;
|
||||
}> = React.memo(({ menuHeader, onChange, text }) => {
|
||||
// if (typeof menuHeader === 'string') {
|
||||
// return <DropdownMenuLabel>{menuHeader}</DropdownMenuLabel>;
|
||||
// }
|
||||
if (typeof menuHeader === 'string') {
|
||||
return <DropdownMenuLabel>{menuHeader}</DropdownMenuLabel>;
|
||||
}
|
||||
if (typeof menuHeader === 'object' && 'placeholder' in menuHeader) {
|
||||
return (
|
||||
<DropdownMenuHeaderSearch
|
||||
placeholder={menuHeader.placeholder}
|
||||
onChange={onChange}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
return <DropdownMenuHeaderSearch placeholder={menuHeader} onChange={onChange} text={text} />;
|
||||
}
|
||||
return menuHeader;
|
||||
});
|
||||
|
|
|
@ -47,7 +47,11 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||
));
|
||||
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
||||
|
||||
const baseContentClass = `bg-background text-foreground 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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1`;
|
||||
const baseContentClass = cn(
|
||||
`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 z-50 min-w-[8rem] overflow-hidden `,
|
||||
'bg-background text-foreground ',
|
||||
'rounded-md border p-1'
|
||||
);
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
|
@ -63,17 +67,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
|
|||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(baseContentClass, 'shadow-md', className)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
));
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
|
||||
footerContent?: React.ReactNode;
|
||||
}
|
||||
>(({ className, children, sideOffset = 4, footerContent, ...props }, ref) => {
|
||||
const NodeWrapper = footerContent ? 'div' : React.Fragment;
|
||||
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(baseContentClass, 'shadow-md', footerContent && 'p-0', className)}
|
||||
{...props}>
|
||||
<NodeWrapper className={cn(footerContent && 'p-2')}>{children}</NodeWrapper>
|
||||
{footerContent && <div className="border-t p-2">{footerContent}</div>}
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
});
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from './AppDropdownSelect';
|
||||
export * from './DropdownLabel';
|
||||
export * from './Dropdown';
|
||||
|
|
|
@ -23,14 +23,14 @@ export const Default: Story = {
|
|||
// Error state
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
children: <div>This content won't be visible due to error</div>
|
||||
children: <div>{`This content won't be visible due to error`} </div>
|
||||
},
|
||||
parameters: {
|
||||
error: new Error('Simulated error for story')
|
||||
error: new Error(`Simulated error for story`)
|
||||
},
|
||||
render: (args) => {
|
||||
const ErrorTrigger = () => {
|
||||
throw new Error('Simulated error for story');
|
||||
throw new Error(`Simulated error for story`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { BusterResizeableGridRow } from './interfaces';
|
||||
import { BusterResizeColumns } from './BusterResizeColumns';
|
||||
import clsx from 'clsx';
|
||||
import { BusterNewItemDropzone } from './_BusterBusterNewItemDropzone';
|
||||
import { MIN_ROW_HEIGHT, TOP_SASH_ID, NEW_ROW_ID, MAX_ROW_HEIGHT } from './config';
|
||||
import { createStyles } from 'antd-style';
|
||||
import clamp from 'lodash/clamp';
|
||||
import { useDebounceFn, useMemoizedFn, useUpdateLayoutEffect } from 'ahooks';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
|
@ -58,7 +56,7 @@ export const BusterResizeRows: React.FC<{
|
|||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
className,
|
||||
'buster-resize-row relative',
|
||||
'mb-10 flex h-full w-full flex-col space-y-3 transition',
|
||||
|
@ -120,7 +118,6 @@ const ResizeRowHandle: React.FC<{
|
|||
hideDropzone?: boolean;
|
||||
}> = React.memo(
|
||||
({ hideDropzone, top, id, active, allowEdit, setIsDraggingResizeId, index, sizes, onResize }) => {
|
||||
const { styles } = useStyles();
|
||||
const { setNodeRef, isOver, over } = useDroppable({
|
||||
id: `${NEW_ROW_ID}_${id}}`,
|
||||
disabled: !allowEdit,
|
||||
|
@ -193,24 +190,3 @@ const ResizeRowHandle: React.FC<{
|
|||
);
|
||||
|
||||
ResizeRowHandle.displayName = 'ResizeRowHandle';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
hitArea: css`
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 54px; // Reduced from 54px to be more reasonable
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
pointer-events: none;
|
||||
|
||||
opacity: 0.2;
|
||||
|
||||
&.top {
|
||||
top: -36px; // Position the hit area to straddle the dragger
|
||||
}
|
||||
|
||||
&:not(.top) {
|
||||
bottom: -15px; // Position the hit area to straddle the dragger
|
||||
}
|
||||
`
|
||||
}));
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { BusterResizeableGrid } from './BusterResizeableGrid';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { AppDataSourceIcon } from './AppDataSourceIcons';
|
||||
import { DataSourceTypes } from '@/api/asset_interfaces';
|
||||
|
||||
const meta: Meta<typeof AppDataSourceIcon> = {
|
||||
title: 'Base/Icons/DataSourceIcon',
|
||||
component: AppDataSourceIcon,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
type: {
|
||||
control: 'select',
|
||||
options: Object.values(DataSourceTypes),
|
||||
description: 'The type of data source to display'
|
||||
},
|
||||
size: {
|
||||
control: 'number',
|
||||
description: 'Size of the icon in pixels'
|
||||
},
|
||||
onClick: {
|
||||
description: 'Optional click handler'
|
||||
},
|
||||
className: {
|
||||
description: 'Optional CSS class name'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AppDataSourceIcon>;
|
||||
|
||||
// Base story showing all data source icons
|
||||
export const AllDataSourceIcons: Story = {
|
||||
render: () => (
|
||||
<div className="grid grid-cols-4 gap-4 p-4">
|
||||
{Object.values(DataSourceTypes).map((type) => (
|
||||
<div key={type} className="flex flex-col items-center gap-2">
|
||||
<AppDataSourceIcon type={type} size={32} />
|
||||
<span className="text-sm">{type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
// Individual icon stories
|
||||
export const PostgresIcon: Story = {
|
||||
args: {
|
||||
type: DataSourceTypes.postgres,
|
||||
size: 32
|
||||
}
|
||||
};
|
||||
|
||||
export const MySQLIcon: Story = {
|
||||
args: {
|
||||
type: DataSourceTypes.mysql,
|
||||
size: 32
|
||||
}
|
||||
};
|
||||
|
||||
export const SnowflakeIcon: Story = {
|
||||
args: {
|
||||
type: DataSourceTypes.snowflake,
|
||||
size: 32
|
||||
}
|
||||
};
|
||||
|
||||
export const BigQueryIcon: Story = {
|
||||
args: {
|
||||
type: DataSourceTypes.bigquery,
|
||||
size: 32
|
||||
}
|
||||
};
|
||||
|
||||
// Interactive example with different sizes
|
||||
export const InteractiveSizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center gap-4">
|
||||
<AppDataSourceIcon type={DataSourceTypes.postgres} size={16} />
|
||||
<AppDataSourceIcon type={DataSourceTypes.postgres} size={24} />
|
||||
<AppDataSourceIcon type={DataSourceTypes.postgres} size={32} />
|
||||
<AppDataSourceIcon type={DataSourceTypes.postgres} size={48} />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
// Clickable example
|
||||
export const Clickable: Story = {
|
||||
args: {
|
||||
type: DataSourceTypes.postgres,
|
||||
size: 32,
|
||||
onClick: () => alert('Icon clicked!')
|
||||
}
|
||||
};
|
|
@ -12,6 +12,7 @@ import { AthenaIcon } from './customIcons/athena';
|
|||
import React from 'react';
|
||||
import { DataSourceTypes } from '@/api/asset_interfaces';
|
||||
import { AppMaterialIcons } from './AppMaterialIcons';
|
||||
import { Database } from './NucleoIconOutlined';
|
||||
|
||||
const IconRecord: Record<DataSourceTypes, any> = {
|
||||
[DataSourceTypes.postgres]: PostgresIcon,
|
||||
|
@ -36,10 +37,8 @@ export const AppDataSourceIcon: React.FC<{
|
|||
}> = ({ type, ...props }) => {
|
||||
const ChosenIcon = IconRecord[type];
|
||||
|
||||
console;
|
||||
|
||||
if (!ChosenIcon) {
|
||||
return <AppMaterialIcons {...props} icon="database" />;
|
||||
return <Database />;
|
||||
}
|
||||
|
||||
return <ChosenIcon {...props} />;
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = createStyles(({ token }) => {
|
||||
return {
|
||||
ringContainer: {
|
||||
position: 'relative'
|
||||
},
|
||||
ringring: {
|
||||
borderRadius: '50%',
|
||||
border: `2px solid ${token.colorPrimary}`,
|
||||
position: 'absolute',
|
||||
animation: 'pulsate 1s ease-out',
|
||||
animationIterationCount: 'infinite',
|
||||
opacity: 0.0,
|
||||
height: '12px',
|
||||
width: '12px',
|
||||
left: '-6px',
|
||||
top: '-6px',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
},
|
||||
circle: {
|
||||
borderRadius: '50%',
|
||||
backgroundColor: token.colorPrimary,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const PulsingDot: React.FC<{
|
||||
size?: number;
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
}> = ({ style, size = 7, color }) => {
|
||||
const { cx, styles } = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={cx('pulsing-dot relative', styles.ringContainer)} style={style}>
|
||||
<span
|
||||
className={cx(styles.ringring, 'animate-pulse')}
|
||||
style={{
|
||||
// height: size * 1.35,
|
||||
// width: size * 1.35,
|
||||
// top: -(size * 1.5155) / 2,
|
||||
// left: -(size * 1.5475) / 2,
|
||||
borderColor: color
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className={cx(styles.circle)}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: color
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,169 +0,0 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Avatar, AvatarProps } from 'antd';
|
||||
import { getFirstTwoCapitalizedLetters } from '@/lib/text';
|
||||
import { AppTooltip } from '../tooltip';
|
||||
import type { GroupProps } from 'antd/es/avatar';
|
||||
import BusterIconWhite from '@/assets/png/buster_icon_small_white.png';
|
||||
import BusterIconBlack from '@/assets/png/buster_icon_small_black.png';
|
||||
import { createStyles } from 'antd-style';
|
||||
export interface BusterUserAvatarProps extends AvatarProps {
|
||||
color?: string;
|
||||
image?: string;
|
||||
name?: string | null;
|
||||
style?: React.CSSProperties;
|
||||
useToolTip?: boolean;
|
||||
}
|
||||
|
||||
export const BusterUserAvatar = React.memo(
|
||||
({ useToolTip = true, ...props }: BusterUserAvatarProps) => {
|
||||
const { size = 38, ...restProps } = props;
|
||||
if (!props.name && !props.image) return <BusterAvatar {...restProps} size={size as number} />;
|
||||
|
||||
const firstAndLastInitial = createNameLetters(props.name, props.image);
|
||||
|
||||
return (
|
||||
<AppTooltip
|
||||
title={useToolTip ? props.name || '' : ''}
|
||||
mouseEnterDelay={0.35}
|
||||
performant={useToolTip}>
|
||||
<Avatar
|
||||
{...props}
|
||||
className={`${props.className || ''} flex ${props.image ? 'bg-transparent!' : ''}`}
|
||||
size={size}
|
||||
style={{
|
||||
minWidth: size as number,
|
||||
minHeight: size as number
|
||||
}}
|
||||
src={createAvatarImage(props.image, firstAndLastInitial, props.name)}>
|
||||
{firstAndLastInitial}
|
||||
</Avatar>
|
||||
</AppTooltip>
|
||||
);
|
||||
}
|
||||
);
|
||||
BusterUserAvatar.displayName = 'BusterUserAvatar';
|
||||
|
||||
const useStyles = createStyles(({ css, token, isDarkMode }) => {
|
||||
return {
|
||||
groupAvatar: css`
|
||||
.busterv2-avatar {
|
||||
font-size: ${token.fontSizeSM} !important;
|
||||
}
|
||||
`,
|
||||
avatar: {
|
||||
background: isDarkMode ? token.colorBgSpotlight : token.colorFillContentHover
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const BusterUserAvatarGroup: React.FC<
|
||||
{
|
||||
avatars: BusterUserAvatarProps[];
|
||||
} & GroupProps
|
||||
> = React.memo(({ avatars, ...props }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const renderedAvatars = useMemo(
|
||||
() =>
|
||||
avatars.map((avatar, index) => (
|
||||
<BusterUserAvatar size={props.size} key={index} {...avatar} />
|
||||
)),
|
||||
[avatars, props.size]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar.Group {...props} className={cx(styles.groupAvatar, props.className)}>
|
||||
{renderedAvatars}
|
||||
</Avatar.Group>
|
||||
</>
|
||||
);
|
||||
});
|
||||
BusterUserAvatarGroup.displayName = 'BusterUserAvatarGroup';
|
||||
|
||||
const createNameLetters = (name?: string | null, image?: string | null | React.ReactNode) => {
|
||||
if (name && !image) {
|
||||
const firstTwoLetters = getFirstTwoCapitalizedLetters(name);
|
||||
if (firstTwoLetters.length == 2) return firstTwoLetters;
|
||||
|
||||
//Get First Name Initial
|
||||
const _name = name.split(' ') as [string, string];
|
||||
const returnName = `${_name[0][0]}`.toUpperCase().replace('@', '');
|
||||
|
||||
return returnName;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const createAvatarImage = (
|
||||
image: string | null | React.ReactNode,
|
||||
initialNames?: null | string,
|
||||
fullname?: string | null
|
||||
) => {
|
||||
if (image) return image;
|
||||
if (!initialNames) return;
|
||||
//USER AVATARS
|
||||
|
||||
// const removeHash = hex.replace('#', '');
|
||||
// const baseUrl = `https://ui-avatars.com/api/?background=${removeHash}&color=fff&name=${name}`;
|
||||
// return baseUrl;
|
||||
|
||||
//VERCEL
|
||||
// const baseUrl = `https://avatar.vercel.sh/${fullname}.svg?text=${initialNames}`;
|
||||
// return baseUrl;
|
||||
|
||||
//DICE BEAR INDENTICON
|
||||
const baseUrl = `https://api.dicebear.com/9.x/initials/svg?seed=${fullname || initialNames}`;
|
||||
return baseUrl;
|
||||
};
|
||||
|
||||
export const BusterAvatar: React.FC<{
|
||||
size?: number;
|
||||
shape?: 'circle' | 'square';
|
||||
}> = React.memo(({ size = 24, shape = 'circle' }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
|
||||
const memoizedStyles = useMemo(
|
||||
() => ({
|
||||
minWidth: size,
|
||||
minHeight: size
|
||||
}),
|
||||
[size]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppTooltip title={'Buster'}>
|
||||
<Avatar
|
||||
size={size}
|
||||
shape={shape}
|
||||
icon={<BusterImage />}
|
||||
className={cx(styles.avatar)}
|
||||
style={memoizedStyles}
|
||||
/>
|
||||
</AppTooltip>
|
||||
);
|
||||
});
|
||||
BusterAvatar.displayName = 'BusterAvatar';
|
||||
|
||||
const BusterImage: React.FC = () => {
|
||||
const isDarkMode = false;
|
||||
const image = isDarkMode ? BusterIconBlack.src : BusterIconWhite.src;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-full w-full items-center justify-center ${
|
||||
isDarkMode ? 'bg-gray-900' : 'bg-white'
|
||||
}`}>
|
||||
<img
|
||||
className="flex items-center justify-center"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: 'auto',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
src={image}
|
||||
alt="Company Logo"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
export * from './BusterUserAvatar';
|
|
@ -4,7 +4,6 @@ export * from './icons';
|
|||
export * from './loaders';
|
||||
export * from './inputs';
|
||||
export * from './tooltip';
|
||||
export * from './image';
|
||||
export * from './select';
|
||||
export * from './segmented';
|
||||
export * from './card';
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { AppCodeEditor } from './AppCodeEditor';
|
||||
|
||||
const meta: Meta<typeof AppCodeEditor> = {
|
||||
title: 'Base/Inputs/AppCodeEditor',
|
||||
component: AppCodeEditor,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="min-w-[500px]">
|
||||
<Story />
|
||||
</div>
|
||||
)
|
||||
]
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AppCodeEditor>;
|
||||
|
||||
const sampleSQLCode = `SELECT users.name, orders.order_date
|
||||
FROM users
|
||||
JOIN orders ON users.id = orders.user_id
|
||||
WHERE orders.status = 'completed'
|
||||
ORDER BY orders.order_date DESC;`;
|
||||
|
||||
const sampleYAMLCode = `version: '3'
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./src:/usr/share/nginx/html`;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: sampleSQLCode,
|
||||
height: '300px',
|
||||
language: 'pgsql',
|
||||
variant: 'bordered'
|
||||
}
|
||||
};
|
||||
|
||||
export const ReadOnly: Story = {
|
||||
args: {
|
||||
value: sampleSQLCode,
|
||||
height: '300px',
|
||||
language: 'pgsql',
|
||||
readOnly: true,
|
||||
variant: 'bordered',
|
||||
readOnlyMessage: 'This is a read-only view'
|
||||
}
|
||||
};
|
||||
|
||||
export const YAMLEditor: Story = {
|
||||
args: {
|
||||
value: sampleYAMLCode,
|
||||
height: '300px',
|
||||
language: 'yaml',
|
||||
variant: 'bordered'
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomHeight: Story = {
|
||||
args: {
|
||||
value: sampleSQLCode,
|
||||
height: '500px',
|
||||
language: 'pgsql',
|
||||
variant: 'bordered'
|
||||
}
|
||||
};
|
||||
|
||||
export const EmptyEditor: Story = {
|
||||
args: {
|
||||
height: '200px',
|
||||
language: 'pgsql',
|
||||
variant: 'bordered'
|
||||
}
|
||||
};
|
|
@ -6,8 +6,8 @@
|
|||
import React, { forwardRef, useMemo } from 'react';
|
||||
import type { editor } from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { CircleSpinnerLoaderContainer } from '../../loaders/CircleSpinnerLoaderContainer';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
|
||||
import './MonacoWebWorker';
|
||||
import { configureMonacoToUseYaml } from './yamlHelper';
|
||||
|
@ -19,19 +19,6 @@ import { configureMonacoToUseYaml } from './yamlHelper';
|
|||
import { Editor as DynamicEditor } from '@monaco-editor/react';
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
const useStyles = createStyles(({ token }) => ({
|
||||
code: {
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
'--font-app': 'Menlo, Monaco, "Courier New", monospace'
|
||||
},
|
||||
bordered: {
|
||||
border: `0.5px solid ${token.colorBorder}`,
|
||||
borderRadius: `${token.borderRadiusLG}px`,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
}));
|
||||
|
||||
export interface AppCodeEditorProps {
|
||||
className?: string;
|
||||
onChangeEditorHeight?: (height: number) => void;
|
||||
|
@ -75,7 +62,7 @@ export const AppCodeEditor = forwardRef<AppCodeEditorHandle, AppCodeEditorProps>
|
|||
},
|
||||
ref
|
||||
) => {
|
||||
const { cx, styles } = useStyles();
|
||||
// const { cx, styles } = useStyles();
|
||||
|
||||
const isDarkModeContext = useTheme()?.theme === 'dark';
|
||||
const useDarkMode = isDarkMode ?? isDarkModeContext;
|
||||
|
@ -148,9 +135,11 @@ export const AppCodeEditor = forwardRef<AppCodeEditorHandle, AppCodeEditorProps>
|
|||
|
||||
return (
|
||||
<div
|
||||
className={cx('app-code-editor relative h-full w-full border', className, styles.code, {
|
||||
[styles.bordered]: variant === 'bordered'
|
||||
})}
|
||||
className={cn(
|
||||
'app-code-editor relative h-full w-full border',
|
||||
variant === 'bordered' && 'overflow-hidden border',
|
||||
className
|
||||
)}
|
||||
style={style}>
|
||||
<DynamicEditor
|
||||
key={useDarkMode ? 'dark' : 'light'}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Input } from 'antd';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
interface AppSearchInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onBlur?: (value: string) => void;
|
||||
onPressEnter?: (value: string) => void;
|
||||
onSearch: (e?: string) => void;
|
||||
enterButton?: string | ReactNode | boolean;
|
||||
loading?: boolean;
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const AppSearchInput: React.FC<AppSearchInputProps> = ({
|
||||
size = 'middle',
|
||||
onChange,
|
||||
onSearch,
|
||||
...props
|
||||
}) => {
|
||||
const onBlurEvent = useMemoizedFn((e: React.FocusEvent<HTMLInputElement, Element>) => {
|
||||
props.onBlur?.(e.target.value);
|
||||
});
|
||||
|
||||
const onPressEnterEvent = useMemoizedFn((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
props.onPressEnter && props.onPressEnter(props.value);
|
||||
});
|
||||
|
||||
return (
|
||||
<Input.Search
|
||||
{...props}
|
||||
onBlur={onBlurEvent}
|
||||
onPressEnter={(e) => onPressEnterEvent(e)}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
size={size}
|
||||
onSearch={(e) => onSearch(e)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
AppSearchInput.displayName = 'AppSearchInput';
|
|
@ -1,29 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { Input, InputProps, InputRef } from 'antd';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { AppMaterialIcons } from '../icons';
|
||||
|
||||
interface SearchInputProps extends Omit<InputProps, 'onChange'> {
|
||||
placeholder: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export const SearchInput = React.forwardRef<InputRef, SearchInputProps>(
|
||||
({ onChange, ...rest }, ref) => {
|
||||
const onChangePreflight = useMemoizedFn((e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.target.value);
|
||||
});
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
prefix={<AppMaterialIcons icon="search" />}
|
||||
onChange={onChangePreflight}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SearchInput.displayName = 'SearchInput';
|
|
@ -1,2 +1 @@
|
|||
export * from './AppSearchInput';
|
||||
export * from './SearchInput';
|
||||
export * from './Input';
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
useBusterCollectionIndividualContextSelector,
|
||||
useCollectionIndividual
|
||||
} from '@/context/Collections';
|
||||
import { useBusterCollectionIndividualContextSelector } from '@/context/Collections';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AppMaterialIcons, BusterUserAvatar } from '@/components/ui';
|
||||
import { AppMaterialIcons } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { createBusterRoute, BusterRoutes } from '@/routes';
|
||||
import { formatDate } from '@/lib';
|
||||
import {
|
||||
|
@ -93,11 +91,7 @@ const columns: BusterListColumn[] = [
|
|||
width: 50,
|
||||
render: (created_by: BusterCollectionListItem['owner']) => {
|
||||
return (
|
||||
<BusterUserAvatar
|
||||
image={created_by?.avatar_url || undefined}
|
||||
name={created_by?.name}
|
||||
size={18}
|
||||
/>
|
||||
<Avatar image={created_by?.avatar_url || undefined} name={created_by?.name} size={18} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AppContent, BusterUserAvatar } from '@/components/ui';
|
||||
import { AppContent } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate, makeHumanReadble } from '@/lib';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { useBusterCollectionListContextSelector } from '@/context/Collections';
|
||||
|
@ -74,9 +75,7 @@ const columns: BusterListColumn[] = [
|
|||
title: 'Owner',
|
||||
width: 50,
|
||||
render: (owner: BusterCollectionListItem['owner']) => {
|
||||
return (
|
||||
<BusterUserAvatar image={owner?.avatar_url || undefined} name={owner?.name} size={18} />
|
||||
);
|
||||
return <Avatar image={owner?.avatar_url || undefined} name={owner?.name} size={18} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import { AppContent } from '@/components/ui/layout/AppContent';
|
||||
import { useBusterDashboardContextSelector } from '@/context/Dashboards';
|
||||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate } from '@/lib';
|
||||
import { BusterList, BusterListColumn, BusterListRow } from '@/components/ui/list';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
|
@ -45,7 +45,7 @@ const columns: BusterListColumn[] = [
|
|||
title: 'Owner',
|
||||
width: 55,
|
||||
render: (_, data) => {
|
||||
return <BusterUserAvatar image={data?.avatar_url} name={data?.name} size={18} />;
|
||||
return <Avatar image={data?.avatar_url} name={data?.name} size={18} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { AppContent } from '@/components/ui/layout/AppContent';
|
||||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate } from '@/lib';
|
||||
import { BusterList, BusterListColumn, BusterListRow } from '@/components/ui/list';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
|
@ -45,11 +45,7 @@ const columns: BusterListColumn[] = [
|
|||
width: 60,
|
||||
render: (_, dataset: BusterDatasetListItem) => (
|
||||
<div className="flex w-full justify-start">
|
||||
<BusterUserAvatar
|
||||
image={dataset.owner.avatar_url || undefined}
|
||||
name={dataset.owner.name}
|
||||
size={18}
|
||||
/>
|
||||
<Avatar image={dataset.owner.avatar_url || undefined} name={dataset.owner.name} size={18} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import { ShareAssetType, VerificationStatus, BusterMetricListItem } from '@/api/
|
|||
import { makeHumanReadble, formatDate } from '@/lib';
|
||||
import React, { memo, useMemo, useRef, useState } from 'react';
|
||||
import { StatusBadgeIndicator, getShareStatus } from '@/components/features/Lists';
|
||||
import { BusterUserAvatar, Text } from '@/components/ui';
|
||||
import { Text } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { BusterRoutes, createBusterRoute } from '@/routes';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { BusterListColumn, BusterListRow } from '@/components/ui/list';
|
||||
|
@ -196,7 +197,7 @@ TitleCell.displayName = 'TitleCell';
|
|||
|
||||
const OwnerCell = memo<{ name: string; image: string | undefined }>(({ name, image }) => (
|
||||
<div className="flex pl-0">
|
||||
<BusterUserAvatar image={image} name={name} size={18} />
|
||||
<Avatar image={image} name={name} size={18} />
|
||||
</div>
|
||||
));
|
||||
OwnerCell.displayName = 'OwnerCell';
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { BusterTerm } from '@/api/buster_rest';
|
||||
import { AppTooltip, AppMaterialIcons } from '@/components/ui';
|
||||
import { AppDropdownSelectProps, AppDropdownSelect } from '@/components/ui/dropdown';
|
||||
import { Dropdown, type DropdownProps } from '@/components/ui/dropdown';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import { Text } from '@/components/ui';
|
||||
import { useGetDatasets } from '@/api/buster_rest/datasets';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
datasetItem: css`
|
||||
|
@ -64,20 +65,18 @@ export const DatasetList: React.FC<{
|
|||
});
|
||||
DatasetList.displayName = 'DatasetList';
|
||||
|
||||
const memoizedTrigger: AppDropdownSelectProps['trigger'] = ['click'];
|
||||
|
||||
const DropdownSelect: React.FC<{
|
||||
children: React.ReactNode;
|
||||
datasets: BusterTerm['datasets'];
|
||||
onChange: (datasets: string[]) => void;
|
||||
placement?: AppDropdownSelectProps['placement'];
|
||||
}> = ({ onChange, children, datasets, placement = 'bottomRight' }) => {
|
||||
}> = ({ onChange, children, datasets }) => {
|
||||
const { data: datasetsList } = useGetDatasets();
|
||||
|
||||
const itemsDropdown = useMemo(() => {
|
||||
return datasetsList.map((item) => ({
|
||||
key: item.id,
|
||||
const itemsDropdown: DropdownProps['items'] = useMemo(() => {
|
||||
return datasetsList.map<DropdownProps['items'][number]>((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
selected: datasets.some((i) => i.id === item.id),
|
||||
onClick: async () => {
|
||||
const isSelected = datasets.find((i) => i.id === item.id);
|
||||
const newDatasets = isSelected
|
||||
|
@ -88,20 +87,20 @@ const DropdownSelect: React.FC<{
|
|||
}));
|
||||
}, [datasets, datasetsList]);
|
||||
|
||||
const selectedItems = useMemo(() => {
|
||||
return datasets.map((item) => item.id);
|
||||
}, [datasets]);
|
||||
const onSelect = useMemoizedFn((itemId: string) => {
|
||||
alert('This feature is not implemented yet');
|
||||
});
|
||||
|
||||
return (
|
||||
<AppDropdownSelect
|
||||
placement={placement}
|
||||
<Dropdown
|
||||
align={'start'}
|
||||
side="bottom"
|
||||
selectType="multiple"
|
||||
items={itemsDropdown}
|
||||
destroyPopupOnHide
|
||||
headerContent={'Related datasets...'}
|
||||
selectedItems={selectedItems}
|
||||
trigger={memoizedTrigger}>
|
||||
onSelect={onSelect}
|
||||
menuHeader={'Related datasets...'}>
|
||||
{children}
|
||||
</AppDropdownSelect>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useBusterTermsIndividualContextSelector, useBusterTermsIndividual } from '@/context/Terms';
|
||||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate } from '@/lib';
|
||||
import { Text } from '@/components/ui';
|
||||
import { DatasetList } from './TermDatasetSelect';
|
||||
|
@ -44,7 +44,7 @@ export const TermIndividualContentSider: React.FC<{ termId: string }> = ({ termI
|
|||
</Text>
|
||||
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<BusterUserAvatar size={24} name={selectedTerm?.created_by.name} />
|
||||
<Avatar size={24} name={selectedTerm?.created_by.name} />
|
||||
<Text>{selectedTerm?.created_by.name}</Text>
|
||||
<Text type="secondary">
|
||||
(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AppContent } from '@/components/ui/layout/AppContent';
|
||||
import { BusterUserAvatar } from '@/components/ui';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { formatDate } from '@/lib';
|
||||
import {
|
||||
ListEmptyStateWithButton,
|
||||
|
@ -38,9 +38,7 @@ const columns: BusterListColumn[] = [
|
|||
dataIndex: 'owner',
|
||||
title: 'Owner',
|
||||
width: 60,
|
||||
render: (_, data: BusterTermListItem) => (
|
||||
<BusterUserAvatar name={data.created_by.name} size={18} />
|
||||
)
|
||||
render: (_, data: BusterTermListItem) => <Avatar name={data.created_by.name} size={18} />
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BusterUserAvatar, BusterAvatar } from '@/components/ui/image';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -13,9 +13,9 @@ export const MessageContainer: React.FC<{
|
|||
return (
|
||||
<div className={cx('flex w-full space-x-2 overflow-hidden')}>
|
||||
{senderName ? (
|
||||
<BusterUserAvatar size={24} name={senderName} src={senderAvatar} useToolTip={true} />
|
||||
<Avatar size={24} name={senderName} image={senderAvatar || ''} useToolTip={true} />
|
||||
) : (
|
||||
<BusterAvatar size={24} />
|
||||
<Avatar size={24} />
|
||||
)}
|
||||
<div className={cx(className, 'px-1')}>{children}</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue