From d3081a6e57a86ef1ff79325e14c5623476b2cdf7 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Wed, 26 Feb 2025 23:25:53 -0700 Subject: [PATCH] rip out app select component --- .../ChatItemsContainer.tsx | 5 +- .../_LayoutHeaderAndSegment/UserHeader.tsx | 5 +- .../(restricted-width)/profile/page.tsx | 4 +- .../Dropdowns/SaveToCollectionsDropdown.tsx | 31 ++-- .../Dropdowns/SaveToDashboardDropdown.tsx | 36 ++-- .../features/Lists/ListUserItem.tsx | 5 +- .../ShareMenu/IndividualSharePerson.tsx | 4 +- web/src/components/ui/avatar/Avatar.tsx | 14 +- web/src/components/ui/avatar/AvatarBase.tsx | 1 - web/src/components/ui/avatar/index.ts | 1 + web/src/components/ui/card/CodeCard.tsx | 20 +-- .../ui/dropdown/Dropdown.stories.tsx | 40 ++++- web/src/components/ui/dropdown/Dropdown.tsx | 23 ++- .../components/ui/dropdown/DropdownBase.tsx | 36 ++-- web/src/components/ui/dropdown/index.ts | 2 +- .../ui/error/GlobalErrorComponent.stories.tsx | 6 +- .../components/ui/grid/BusterResizeRows.tsx | 26 +-- .../ui/grid/BusterResizeableGrid.stories.tsx | 1 + .../ui/icons/AppDataSourceIcons.stories.tsx | 96 ++++++++++ .../ui/icons/AppDataSourceIcons.tsx | 5 +- web/src/components/ui/icons/PulsingDot.tsx | 64 ------- .../components/ui/image/BusterUserAvatar.tsx | 169 ------------------ web/src/components/ui/image/index.ts | 1 - web/src/components/ui/index.ts | 1 - .../AppCodeEditor/AppCodeEditor.stories.tsx | 82 +++++++++ .../ui/inputs/AppCodeEditor/AppCodeEditor.tsx | 25 +-- .../components/ui/inputs/AppSearchInput.tsx | 44 ----- web/src/components/ui/inputs/SearchInput.tsx | 29 --- web/src/components/ui/inputs/index.ts | 3 +- .../CollectionIndividualContent.tsx | 14 +- .../CollectionsListContent.tsx | 7 +- .../DashboardListContent.tsx | 4 +- .../DatasetListContent.tsx | 8 +- .../MetricItemsContainer.tsx | 5 +- .../TermDatasetSelect.tsx | 35 ++-- .../TermIndividualContentSider.tsx | 4 +- .../TermsListController/TermsListContent.tsx | 6 +- .../ChatContent/MessageContainer.tsx | 6 +- 38 files changed, 350 insertions(+), 518 deletions(-) create mode 100644 web/src/components/ui/avatar/index.ts create mode 100644 web/src/components/ui/icons/AppDataSourceIcons.stories.tsx delete mode 100644 web/src/components/ui/icons/PulsingDot.tsx delete mode 100644 web/src/components/ui/image/BusterUserAvatar.tsx delete mode 100644 web/src/components/ui/image/index.ts create mode 100644 web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.stories.tsx delete mode 100644 web/src/components/ui/inputs/AppSearchInput.tsx delete mode 100644 web/src/components/ui/inputs/SearchInput.tsx diff --git a/web/src/app/app/chats/_ChatsListContainer/ChatItemsContainer.tsx b/web/src/app/app/chats/_ChatsListContainer/ChatItemsContainer.tsx index 644e7adb5..77c9dd080 100644 --- a/web/src/app/app/chats/_ChatsListContainer/ChatItemsContainer.tsx +++ b/web/src/app/app/chats/_ChatsListContainer/ChatItemsContainer.tsx @@ -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 }) => (
- +
)); OwnerCell.displayName = 'OwnerCell'; diff --git a/web/src/app/app/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx b/web/src/app/app/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx index 3426ea48b..31c8616c2 100644 --- a/web/src/app/app/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx +++ b/web/src/app/app/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx @@ -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 (
- +
{user.name} diff --git a/web/src/app/app/settings/(restricted-width)/profile/page.tsx b/web/src/app/app/settings/(restricted-width)/profile/page.tsx index 49c110792..4ccd348b5 100644 --- a/web/src/app/app/settings/(restricted-width)/profile/page.tsx +++ b/web/src/app/app/settings/(restricted-width)/profile/page.tsx @@ -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' }}>
- + {name}
diff --git a/web/src/components/features/Dropdowns/SaveToCollectionsDropdown.tsx b/web/src/components/features/Dropdowns/SaveToCollectionsDropdown.tsx index aaf0a6d4e..05f39861a 100644 --- a/web/src/components/features/Dropdowns/SaveToCollectionsDropdown.tsx +++ b/web/src/components/features/Dropdowns/SaveToCollectionsDropdown.tsx @@ -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((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 ( <> - - {showDropdown ? ( - <>{children} - ) : ( - {children} - )} - + items={items}> + {children} + (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(() => ['click'], []); - const memoizedButton = useMemo(() => { return ( } /> @@ -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={} /> ); } }; + +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: ( + + ), + children: + } +}; diff --git a/web/src/components/ui/dropdown/Dropdown.tsx b/web/src/components/ui/dropdown/Dropdown.tsx index 5a5fd889f..b5d04fa3f 100644 --- a/web/src/components/ui/dropdown/Dropdown.tsx +++ b/web/src/components/ui/dropdown/Dropdown.tsx @@ -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 = React.memo( onOpenChange, emptyStateText = 'No items found', className, + footerContent, ...props }) => { const { filteredItems, searchText, handleSearchChange } = useDebounceSearch({ @@ -133,7 +135,8 @@ export const Dropdown: React.FC = React.memo( + side={side} + footerContent={footerContent}> {menuHeader && ( <> void; text: string; }> = React.memo(({ menuHeader, onChange, text }) => { + // if (typeof menuHeader === 'string') { + // return {menuHeader}; + // } if (typeof menuHeader === 'string') { - return {menuHeader}; - } - if (typeof menuHeader === 'object' && 'placeholder' in menuHeader) { - return ( - - ); + return ; } return menuHeader; }); diff --git a/web/src/components/ui/dropdown/DropdownBase.tsx b/web/src/components/ui/dropdown/DropdownBase.tsx index f8ee2737a..8683d225a 100644 --- a/web/src/components/ui/dropdown/DropdownBase.tsx +++ b/web/src/components/ui/dropdown/DropdownBase.tsx @@ -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, @@ -63,17 +67,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)); + React.ComponentPropsWithoutRef & { + footerContent?: React.ReactNode; + } +>(({ className, children, sideOffset = 4, footerContent, ...props }, ref) => { + const NodeWrapper = footerContent ? 'div' : React.Fragment; + + return ( + + + {children} + {footerContent &&
{footerContent}
} +
+
+ ); +}); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< diff --git a/web/src/components/ui/dropdown/index.ts b/web/src/components/ui/dropdown/index.ts index 03229c13c..89519d015 100644 --- a/web/src/components/ui/dropdown/index.ts +++ b/web/src/components/ui/dropdown/index.ts @@ -1,2 +1,2 @@ -export * from './AppDropdownSelect'; export * from './DropdownLabel'; +export * from './Dropdown'; diff --git a/web/src/components/ui/error/GlobalErrorComponent.stories.tsx b/web/src/components/ui/error/GlobalErrorComponent.stories.tsx index 0953eee8a..455abc463 100644 --- a/web/src/components/ui/error/GlobalErrorComponent.stories.tsx +++ b/web/src/components/ui/error/GlobalErrorComponent.stories.tsx @@ -23,14 +23,14 @@ export const Default: Story = { // Error state export const WithError: Story = { args: { - children:
This content won't be visible due to error
+ children:
{`This content won't be visible due to error`}
}, 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 ( diff --git a/web/src/components/ui/grid/BusterResizeRows.tsx b/web/src/components/ui/grid/BusterResizeRows.tsx index 9729b2b03..747210daf 100644 --- a/web/src/components/ui/grid/BusterResizeRows.tsx +++ b/web/src/components/ui/grid/BusterResizeRows.tsx @@ -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 (
= 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 - } - ` -})); diff --git a/web/src/components/ui/grid/BusterResizeableGrid.stories.tsx b/web/src/components/ui/grid/BusterResizeableGrid.stories.tsx index 930884d54..54fbbc5ed 100644 --- a/web/src/components/ui/grid/BusterResizeableGrid.stories.tsx +++ b/web/src/components/ui/grid/BusterResizeableGrid.stories.tsx @@ -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'; diff --git a/web/src/components/ui/icons/AppDataSourceIcons.stories.tsx b/web/src/components/ui/icons/AppDataSourceIcons.stories.tsx new file mode 100644 index 000000000..83cdf615a --- /dev/null +++ b/web/src/components/ui/icons/AppDataSourceIcons.stories.tsx @@ -0,0 +1,96 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AppDataSourceIcon } from './AppDataSourceIcons'; +import { DataSourceTypes } from '@/api/asset_interfaces'; + +const meta: Meta = { + 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; + +// Base story showing all data source icons +export const AllDataSourceIcons: Story = { + render: () => ( +
+ {Object.values(DataSourceTypes).map((type) => ( +
+ + {type} +
+ ))} +
+ ) +}; + +// 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: () => ( +
+ + + + +
+ ) +}; + +// Clickable example +export const Clickable: Story = { + args: { + type: DataSourceTypes.postgres, + size: 32, + onClick: () => alert('Icon clicked!') + } +}; diff --git a/web/src/components/ui/icons/AppDataSourceIcons.tsx b/web/src/components/ui/icons/AppDataSourceIcons.tsx index db827ac2c..21b0e1b29 100644 --- a/web/src/components/ui/icons/AppDataSourceIcons.tsx +++ b/web/src/components/ui/icons/AppDataSourceIcons.tsx @@ -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.postgres]: PostgresIcon, @@ -36,10 +37,8 @@ export const AppDataSourceIcon: React.FC<{ }> = ({ type, ...props }) => { const ChosenIcon = IconRecord[type]; - console; - if (!ChosenIcon) { - return ; + return ; } return ; diff --git a/web/src/components/ui/icons/PulsingDot.tsx b/web/src/components/ui/icons/PulsingDot.tsx deleted file mode 100644 index 00b30858a..000000000 --- a/web/src/components/ui/icons/PulsingDot.tsx +++ /dev/null @@ -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 ( - <> - - - - - - ); -}; diff --git a/web/src/components/ui/image/BusterUserAvatar.tsx b/web/src/components/ui/image/BusterUserAvatar.tsx deleted file mode 100644 index 45539f785..000000000 --- a/web/src/components/ui/image/BusterUserAvatar.tsx +++ /dev/null @@ -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 ; - - const firstAndLastInitial = createNameLetters(props.name, props.image); - - return ( - - - {firstAndLastInitial} - - - ); - } -); -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) => ( - - )), - [avatars, props.size] - ); - - return ( - <> - - {renderedAvatars} - - - ); -}); -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 ( - - } - className={cx(styles.avatar)} - style={memoizedStyles} - /> - - ); -}); -BusterAvatar.displayName = 'BusterAvatar'; - -const BusterImage: React.FC = () => { - const isDarkMode = false; - const image = isDarkMode ? BusterIconBlack.src : BusterIconWhite.src; - - return ( -
- Company Logo -
- ); -}; diff --git a/web/src/components/ui/image/index.ts b/web/src/components/ui/image/index.ts deleted file mode 100644 index 911a6a128..000000000 --- a/web/src/components/ui/image/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BusterUserAvatar'; diff --git a/web/src/components/ui/index.ts b/web/src/components/ui/index.ts index 769832ff8..671aeb281 100644 --- a/web/src/components/ui/index.ts +++ b/web/src/components/ui/index.ts @@ -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'; diff --git a/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.stories.tsx b/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.stories.tsx new file mode 100644 index 000000000..55b7495e7 --- /dev/null +++ b/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.stories.tsx @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AppCodeEditor } from './AppCodeEditor'; + +const meta: Meta = { + title: 'Base/Inputs/AppCodeEditor', + component: AppCodeEditor, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; + +export default meta; +type Story = StoryObj; + +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' + } +}; diff --git a/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.tsx b/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.tsx index dab5bdb7a..b2d2414c8 100644 --- a/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.tsx +++ b/web/src/components/ui/inputs/AppCodeEditor/AppCodeEditor.tsx @@ -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 }, 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 return (
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 = ({ - size = 'middle', - onChange, - onSearch, - ...props -}) => { - const onBlurEvent = useMemoizedFn((e: React.FocusEvent) => { - props.onBlur?.(e.target.value); - }); - - const onPressEnterEvent = useMemoizedFn((e: React.KeyboardEvent) => { - props.onPressEnter && props.onPressEnter(props.value); - }); - - return ( - onPressEnterEvent(e)} - onChange={(e) => onChange(e.target.value)} - size={size} - onSearch={(e) => onSearch(e)} - /> - ); -}; - -AppSearchInput.displayName = 'AppSearchInput'; diff --git a/web/src/components/ui/inputs/SearchInput.tsx b/web/src/components/ui/inputs/SearchInput.tsx deleted file mode 100644 index 80d389726..000000000 --- a/web/src/components/ui/inputs/SearchInput.tsx +++ /dev/null @@ -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 { - placeholder: string; - onChange: (value: string) => void; -} - -export const SearchInput = React.forwardRef( - ({ onChange, ...rest }, ref) => { - const onChangePreflight = useMemoizedFn((e: ChangeEvent) => { - onChange(e.target.value); - }); - return ( - } - onChange={onChangePreflight} - {...rest} - /> - ); - } -); - -SearchInput.displayName = 'SearchInput'; diff --git a/web/src/components/ui/inputs/index.ts b/web/src/components/ui/inputs/index.ts index 498972c79..ba9fe7ebc 100644 --- a/web/src/components/ui/inputs/index.ts +++ b/web/src/components/ui/inputs/index.ts @@ -1,2 +1 @@ -export * from './AppSearchInput'; -export * from './SearchInput'; +export * from './Input'; diff --git a/web/src/controllers/CollectionIndividualController/CollectionIndividualContent.tsx b/web/src/controllers/CollectionIndividualController/CollectionIndividualContent.tsx index 9a1b3b059..0e439bc30 100644 --- a/web/src/controllers/CollectionIndividualController/CollectionIndividualContent.tsx +++ b/web/src/controllers/CollectionIndividualController/CollectionIndividualContent.tsx @@ -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 ( - + ); } } diff --git a/web/src/controllers/CollectionListController/CollectionsListContent.tsx b/web/src/controllers/CollectionListController/CollectionsListContent.tsx index 709f566b6..44559ef31 100644 --- a/web/src/controllers/CollectionListController/CollectionsListContent.tsx +++ b/web/src/controllers/CollectionListController/CollectionsListContent.tsx @@ -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 ( - - ); + return ; } } ]; diff --git a/web/src/controllers/DashboardListController/DashboardListContent.tsx b/web/src/controllers/DashboardListController/DashboardListContent.tsx index 8481890ac..3139f191c 100644 --- a/web/src/controllers/DashboardListController/DashboardListContent.tsx +++ b/web/src/controllers/DashboardListController/DashboardListContent.tsx @@ -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 ; + return ; } } ]; diff --git a/web/src/controllers/DatasetsListController/DatasetListContent.tsx b/web/src/controllers/DatasetsListController/DatasetListContent.tsx index eea3999d6..0e96eb18a 100644 --- a/web/src/controllers/DatasetsListController/DatasetListContent.tsx +++ b/web/src/controllers/DatasetsListController/DatasetListContent.tsx @@ -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) => (
- +
) } diff --git a/web/src/controllers/MetricListContainer/MetricItemsContainer.tsx b/web/src/controllers/MetricListContainer/MetricItemsContainer.tsx index b485183e5..3f339d256 100644 --- a/web/src/controllers/MetricListContainer/MetricItemsContainer.tsx +++ b/web/src/controllers/MetricListContainer/MetricItemsContainer.tsx @@ -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 }) => (
- +
)); OwnerCell.displayName = 'OwnerCell'; diff --git a/web/src/controllers/TermIndividualController/TermDatasetSelect.tsx b/web/src/controllers/TermIndividualController/TermDatasetSelect.tsx index 24090e0e2..350241f2f 100644 --- a/web/src/controllers/TermIndividualController/TermDatasetSelect.tsx +++ b/web/src/controllers/TermIndividualController/TermDatasetSelect.tsx @@ -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((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 ( - + onSelect={onSelect} + menuHeader={'Related datasets...'}> {children} - + ); }; diff --git a/web/src/controllers/TermIndividualController/TermIndividualContentSider.tsx b/web/src/controllers/TermIndividualController/TermIndividualContentSider.tsx index 57d458c0d..0505d565e 100644 --- a/web/src/controllers/TermIndividualController/TermIndividualContentSider.tsx +++ b/web/src/controllers/TermIndividualController/TermIndividualContentSider.tsx @@ -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
- + {selectedTerm?.created_by.name} ( diff --git a/web/src/controllers/TermsListController/TermsListContent.tsx b/web/src/controllers/TermsListController/TermsListContent.tsx index 555b94fe2..d312970dc 100644 --- a/web/src/controllers/TermsListController/TermsListContent.tsx +++ b/web/src/controllers/TermsListController/TermsListContent.tsx @@ -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) => ( - - ) + render: (_, data: BusterTermListItem) => } ]; diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/MessageContainer.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/MessageContainer.tsx index 99fe1a024..4ffa9d2c0 100644 --- a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/MessageContainer.tsx +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/MessageContainer.tsx @@ -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 (
{senderName ? ( - + ) : ( - + )}
{children}