make more story components

This commit is contained in:
Nate Kelley 2025-02-26 20:46:36 -07:00
parent 0c66223ac6
commit e240fadbb1
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
18 changed files with 334 additions and 103 deletions

View File

@ -178,9 +178,9 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; chatId
); );
TitleCell.displayName = 'TitleCell'; TitleCell.displayName = 'TitleCell';
const OwnerCell = memo<{ name: string; image: string | null | undefined }>(({ name, image }) => ( const OwnerCell = memo<{ name: string; image: string | undefined }>(({ name, image }) => (
<div className="flex pl-0"> <div className="flex pl-0">
<BusterUserAvatar image={image || undefined} name={name} size={18} /> <BusterUserAvatar image={image} name={name} size={18} />
</div> </div>
)); ));
OwnerCell.displayName = 'OwnerCell'; OwnerCell.displayName = 'OwnerCell';

View File

@ -30,9 +30,9 @@ const roundingVariants = {
}; };
const sizeVariants = { const sizeVariants = {
default: 'h-7', default: 'h-6',
tall: 'h-8', tall: 'h-7',
small: 'h-6' small: 'h-5'
}; };
export const buttonVariants = cva( export const buttonVariants = cva(

View File

@ -20,7 +20,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
'bg-background text-foreground overflow-hidden rounded border shadow', 'bg-background text-foreground flex flex-col overflow-hidden rounded border shadow',
className className
)} )}
{...props} {...props}

View File

@ -0,0 +1,91 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CodeCard } from './CodeCard';
const meta: Meta<typeof CodeCard> = {
title: 'Base/Cards/CodeCard',
component: CodeCard,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
decorators: [
(Story) => (
<div className="w-full min-w-[500px]">
<Story />
</div>
)
]
};
export default meta;
type Story = StoryObj<typeof CodeCard>;
const sampleCode = `function helloWorld() {
console.log('Hello, World!');
}`;
const longCode = `import React from 'react';
import { Button } from 'antd';
export const MyComponent = () => {
const handleClick = () => {
console.log('Button clicked!');
};
return (
<div className="container">
<h1>Hello World</h1>
<Button onClick={handleClick}>
Click me
</Button>
</div>
);
};`;
export const Default: Story = {
args: {
code: sampleCode,
language: 'javascript',
fileName: 'example.js',
className: 'w-[500px] h-[300px]'
}
};
export const ReadOnly: Story = {
args: {
code: sampleCode,
language: 'javascript',
fileName: 'readonly.js',
readOnly: true,
className: 'w-[500px] h-[300px]'
}
};
export const LargerEditor: Story = {
args: {
code: longCode,
language: 'typescript',
fileName: 'MyComponent.tsx',
className: 'w-[600px] h-[400px]'
}
};
export const NoButtons: Story = {
args: {
code: sampleCode,
language: 'javascript',
fileName: 'no-buttons.js',
buttons: false,
className: 'w-[500px] h-[300px]'
}
};
export const CustomButtons: Story = {
args: {
code: sampleCode,
language: 'javascript',
fileName: 'custom-buttons.js',
buttons: <div className="px-2">Custom Buttons</div>,
className: 'w-[500px] h-[300px]'
}
};

View File

@ -50,6 +50,7 @@ export const CodeCard: React.FC<{
readOnly={readOnly} readOnly={readOnly}
height="100%" height="100%"
onMetaEnter={onMetaEnter} onMetaEnter={onMetaEnter}
className="border-none"
/> />
</div> </div>
</CardContent> </CardContent>

View File

@ -105,7 +105,7 @@ const LegendItemStandard = React.memo(
onMouseEnter={onMouseEnterHandler} onMouseEnter={onMouseEnterHandler}
onMouseLeave={onMouseLeaveHandler} onMouseLeave={onMouseLeaveHandler}
className={cn( className={cn(
'flex flex-col justify-center space-y-0', 'flex flex-col justify-center space-y-1',
'h-[24px] rounded-sm px-2.5', 'h-[24px] rounded-sm px-2.5',
clickable && 'transition-background hover:bg-item-hover cursor-pointer duration-100' clickable && 'transition-background hover:bg-item-hover cursor-pointer duration-100'
)}> )}>

View File

@ -1,33 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react';
export const useMeasureElement = (useMeasure?: boolean) => {
const elementRef = useRef<HTMLDivElement>(null);
const [dimensions, setDimensions] = useState({ fullWidth: 0, fullHeight: 0 });
const updateDimensions = useCallback(() => {
if (elementRef.current) {
const rect = elementRef.current.getBoundingClientRect();
setDimensions({
fullWidth: rect.width,
fullHeight: rect.height
});
}
}, []);
useEffect(() => {
if (!elementRef.current || !useMeasure) return;
const resizeObserver = new ResizeObserver(updateDimensions);
resizeObserver.observe(elementRef.current);
return () => {
resizeObserver.disconnect();
};
}, [updateDimensions, useMeasure]);
return {
elementRef,
...dimensions
};
};

View File

@ -0,0 +1,146 @@
import type { Meta, StoryObj } from '@storybook/react';
import { BusterChartLegend } from '../BusterChartLegend';
import { ChartType } from '../../interfaces';
const meta: Meta<typeof BusterChartLegend> = {
title: 'Base/Charts/BusterChartLegend',
component: BusterChartLegend,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
argTypes: {
onClickItem: { action: 'clicked' },
onFocusItem: { action: 'focused' },
showLegendHeadline: {
control: 'select',
description:
'Show the legend headline will only work if a headline is provided (see example)',
options: [false, 'total', 'average', 'min', 'max', 'current', 'median']
}
}
};
export default meta;
type Story = StoryObj<typeof BusterChartLegend>;
const defaultLegendItems = [
{
color: '#1677FF',
inactive: false,
type: ChartType.Line,
formattedName: 'Revenue',
id: 'revenue',
serieName: 'revenue'
},
{
color: '#52C41A',
inactive: false,
type: ChartType.Line,
formattedName: 'Profit',
id: 'profit',
serieName: 'profit'
},
{
color: '#F5222D',
inactive: false,
type: ChartType.Bar,
formattedName: 'Orders',
id: 'orders',
serieName: 'orders'
}
];
export const Default: Story = {
args: {
legendItems: defaultLegendItems,
show: true,
animateLegend: true,
containerWidth: 600,
showLegendHeadline: false,
onClickItem: () => {},
onFocusItem: () => {}
}
};
export const WithHeadline: Story = {
args: {
legendItems: [
{
...defaultLegendItems[0],
headline: {
type: 'total',
titleAmount: '$1,234,567'
}
},
{
...defaultLegendItems[1],
headline: {
type: 'total',
titleAmount: '$567,890'
}
},
{
...defaultLegendItems[2],
headline: {
type: 'total',
titleAmount: '$98,765'
}
}
],
show: true,
animateLegend: true,
containerWidth: 600,
showLegendHeadline: 'total'
}
};
export const WithOverflow: Story = {
args: {
legendItems: [
...defaultLegendItems,
{
color: '#722ED1',
inactive: false,
type: ChartType.Line,
formattedName: 'Customers',
id: 'customers',
serieName: 'customers'
},
{
color: '#13C2C2',
inactive: false,
type: ChartType.Line,
formattedName: 'Average Order Value',
id: 'aov',
serieName: 'aov'
},
{
color: '#FA8C16',
inactive: false,
type: ChartType.Bar,
formattedName: 'Returns',
id: 'returns',
serieName: 'returns'
}
],
show: true,
animateLegend: true,
containerWidth: 400,
showLegendHeadline: undefined,
onClickItem: () => {},
onFocusItem: () => {}
}
};
export const Hidden: Story = {
args: {
legendItems: defaultLegendItems,
show: false,
animateLegend: true,
containerWidth: 600,
showLegendHeadline: undefined,
onClickItem: () => {},
onFocusItem: () => {}
}
};

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { OverflowButton } from './OverflowContainer'; import { OverflowButton } from '../OverflowContainer';
import { ChartType } from '../interfaces'; import { ChartType } from '../../interfaces';
const meta = { const meta = {
title: 'Base/Charts/OverflowButton', title: 'Base/Charts/OverflowButton',

View File

@ -1,7 +1,8 @@
'use client'; 'use client';
import { ShimmerText, Text } from '@/components/ui'; import { ShimmerText } from '@/components/ui/text/ShimmerText';
import React from 'react'; import React from 'react';
import { cn } from '@/lib/utils';
export const PreparingYourRequestLoader: React.FC<{ export const PreparingYourRequestLoader: React.FC<{
className?: string; className?: string;
@ -12,10 +13,7 @@ export const PreparingYourRequestLoader: React.FC<{
return ( return (
<div className={`flex h-full w-full items-center justify-center space-x-1.5 ${className}`}> <div className={`flex h-full w-full items-center justify-center space-x-1.5 ${className}`}>
{error || useShimmer === false ? ( {error || useShimmer === false ? (
<Text type="tertiary" className="flex items-center text-center"> <span className="text-text-tertiary flex items-center text-center">{error || text}</span>
{/* {!!error && <AppMaterialIcons icon="error" className="mr-1" />} */}
{error || text}
</Text>
) : ( ) : (
<ShimmerText text={text} /> <ShimmerText text={text} />
)} )}
@ -29,9 +27,7 @@ export const NoChartData: React.FC<{
}> = ({ className = '', noDataText = 'The query ran successfully but didnt return any data' }) => { }> = ({ className = '', noDataText = 'The query ran successfully but didnt return any data' }) => {
return ( return (
<div className={`flex h-full w-full items-center justify-center ${className}`}> <div className={`flex h-full w-full items-center justify-center ${className}`}>
<Text type="tertiary" className={`${className}`}> <span className={cn('text-text-tertiary', className)}>{noDataText}</span>
{noDataText}
</Text>
</div> </div>
); );
}; };

View File

@ -1,8 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Text } from '@/components/ui';
import { useMount } from 'ahooks'; import { useMount } from 'ahooks';
import { BusterChartProps, ChartType } from '../interfaces'; import { type BusterChartProps, ChartType } from '../interfaces';
export const NoValidAxis: React.FC<{ export const NoValidAxis: React.FC<{
type: ChartType; type: ChartType;
@ -20,7 +18,7 @@ export const NoValidAxis: React.FC<{
return ( return (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<Text type="tertiary">{inValidChartText}</Text> <span className="text-text-tertiary">{inValidChartText}</span>
</div> </div>
); );
}; };

View File

@ -16,7 +16,6 @@ export const BusterMetricChart: React.FC<BusterMetricChartProps> = React.memo(
metricSubHeader, metricSubHeader,
metricValueAggregate, metricValueAggregate,
data, data,
isDarkMode,
animate, animate,
columnLabelFormats, columnLabelFormats,
metricValueLabel, metricValueLabel,

View File

@ -1,20 +0,0 @@
import React from 'react';
import { AppPopoverMenu, AppPopoverMenuProps } from '../tooltip';
export interface AppDropdownSelectProps extends AppPopoverMenuProps {
items: {
label: React.ReactNode;
key?: string;
index?: number;
onClick?: () => void;
icon?: React.ReactNode;
disabled?: boolean;
link?: string;
}[];
}
export const AppDropdownSelect: React.FC<AppDropdownSelectProps> = React.memo((props) => {
return <AppPopoverMenu {...props} />;
});
AppDropdownSelect.displayName = 'AppDropdownSelect';

View File

@ -0,0 +1,42 @@
import type { Meta, StoryObj } from '@storybook/react';
import { GlobalErrorComponent } from './GlobalErrorComponent';
const meta: Meta<typeof GlobalErrorComponent> = {
title: 'Base/GlobalErrorComponent',
component: GlobalErrorComponent,
parameters: {
layout: 'fullscreen'
},
tags: ['autodocs']
};
export default meta;
type Story = StoryObj<typeof GlobalErrorComponent>;
// Normal state with content
export const Default: Story = {
args: {
children: <div>Normal application content</div>
}
};
// Error state
export const WithError: Story = {
args: {
children: <div>This content won't be visible due to error</div>
},
parameters: {
error: new Error('Simulated error for story')
},
render: (args) => {
const ErrorTrigger = () => {
throw new Error('Simulated error for story');
};
return (
<GlobalErrorComponent {...args}>
<ErrorTrigger />
</GlobalErrorComponent>
);
}
};

View File

@ -1,9 +1,15 @@
'use client'; 'use client';
import { Button } from 'antd';
import { Component, ErrorInfo, ReactNode } from 'react'; import { Component, ErrorInfo, ReactNode } from 'react';
import { Title } from '@/components/ui'; import { Button } from '../buttons/Button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
CardFooter
} from '../card/CardBase';
interface Props { interface Props {
children: ReactNode; children: ReactNode;
} }
@ -31,26 +37,30 @@ export class GlobalErrorComponent extends Component<Props, State> {
<div <div
className="bg-opacity-90 flex min-h-screen w-screen flex-col items-center justify-center bg-linear-to-br from-gray-50 to-gray-200 p-8 backdrop-blur-xs backdrop-brightness-95 backdrop-filter" className="bg-opacity-90 flex min-h-screen w-screen flex-col items-center justify-center bg-linear-to-br from-gray-50 to-gray-200 p-8 backdrop-blur-xs backdrop-brightness-95 backdrop-filter"
role="alert"> role="alert">
<div className="-mt-4 max-w-md rounded-lg border border-gray-200 bg-white/90 p-10"> <Card>
<div className="mb-8 flex flex-col gap-4"> <CardContent>
<Title className="animate-fade-in text-center" level={1}> <div className="flex flex-col gap-4">
Looks like we hit an error! 😅 <h1 className="animate-fade-in text-2xl font-semibold">
</Title> Looks like we hit an error! 😅
</h1>
<Title level={5} className="animate-slide-up m-0! text-gray-600!"> <h5 className="animate-slide-up m-0 text-base font-medium text-gray-600">
Don&apos;t worry, it&apos;s not you - it&apos;s us! Don&apos;t worry, it&apos;s not you - it&apos;s us!
</Title> </h5>
<Title level={5} className="animate-slide-up m-0! text-gray-500!"> <h5 className="animate-slide-up m-0 text-base font-medium text-gray-500">
If this error persists, please contact Buster support! If this error persists, please contact Buster support!
</Title> </h5>
</div> </div>
</CardContent>
<a href="/" className="block"> <CardFooter className="w-full pt-0">
<Button type="primary" block size="large" className="w-full"> <a href="/" className="w-full">
Take Me Home 🏠 <Button variant="black" block size="tall">
</Button> Take Me Home 🏠
</a> </Button>
</div> </a>
</CardFooter>
</Card>
</div> </div>
); );
} }

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import CircleSpinnerLoader from './CircleSpinnerLoader'; import CircleSpinnerLoader from './CircleSpinnerLoader';
import { Text } from '@/components/ui';
export const CircleSpinnerLoaderContainer: React.FC<{ export const CircleSpinnerLoaderContainer: React.FC<{
text?: string; text?: string;
@ -9,7 +8,7 @@ export const CircleSpinnerLoaderContainer: React.FC<{
return ( return (
<div className={`flex h-full w-full flex-col items-center justify-center ${className}`}> <div className={`flex h-full w-full flex-col items-center justify-center ${className}`}>
<CircleSpinnerLoader /> <CircleSpinnerLoader />
{text && <Text className="mt-3">{text}</Text>} {text && <span className="mt-3 text-base">{text}</span>}
</div> </div>
); );
}; };

View File

@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import { Toaster as ToasterSonner } from 'sonner'; import { Toaster as ToasterSonner } from 'sonner';
import { CircleCheck } from '@/components/ui/icons'; import { CircleCheck, CircleXmark, CircleWarning } from '@/components/ui/icons';
type ToasterProps = React.ComponentProps<typeof ToasterSonner>; type ToasterProps = React.ComponentProps<typeof ToasterSonner>;
@ -16,7 +16,9 @@ const Toaster = ({ ...props }: ToasterProps) => {
expand={true} expand={true}
visibleToasts={5} visibleToasts={5}
icons={{ icons={{
success: <CircleCheck /> success: <CircleCheck />,
error: <CircleXmark />,
warning: <CircleWarning />
}} }}
swipeDirections={['right']} swipeDirections={['right']}
theme={theme as ToasterProps['theme']} theme={theme as ToasterProps['theme']}
@ -24,7 +26,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg', 'group toast group-[.toaster]:bg-background! border !p-3 group-[.toaster]:text-foreground! group-[.toaster]:border-border! group-[.toaster]:shadow-hard!',
description: 'group-[.toast]:text-muted-foreground', description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground', actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-border group-[.toast]:text-foreground' cancelButton: 'group-[.toast]:bg-border group-[.toast]:text-foreground'

View File

@ -194,9 +194,9 @@ const TitleCell = React.memo<{ title: string; status: VerificationStatus; metric
); );
TitleCell.displayName = 'TitleCell'; TitleCell.displayName = 'TitleCell';
const OwnerCell = memo<{ name: string; image: string | null | undefined }>(({ name, image }) => ( const OwnerCell = memo<{ name: string; image: string | undefined }>(({ name, image }) => (
<div className="flex pl-0"> <div className="flex pl-0">
<BusterUserAvatar image={image || undefined} name={name} size={18} /> <BusterUserAvatar image={image} name={name} size={18} />
</div> </div>
)); ));
OwnerCell.displayName = 'OwnerCell'; OwnerCell.displayName = 'OwnerCell';