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';
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">
<BusterUserAvatar image={image || undefined} name={name} size={18} />
<BusterUserAvatar image={image} name={name} size={18} />
</div>
));
OwnerCell.displayName = 'OwnerCell';

View File

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

View File

@ -20,7 +20,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
<div
ref={ref}
className={cn(
'bg-background text-foreground overflow-hidden rounded border shadow',
'bg-background text-foreground flex flex-col overflow-hidden rounded border shadow',
className
)}
{...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}
height="100%"
onMetaEnter={onMetaEnter}
className="border-none"
/>
</div>
</CardContent>

View File

@ -105,7 +105,7 @@ const LegendItemStandard = React.memo(
onMouseEnter={onMouseEnterHandler}
onMouseLeave={onMouseLeaveHandler}
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',
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 { OverflowButton } from './OverflowContainer';
import { ChartType } from '../interfaces';
import { OverflowButton } from '../OverflowContainer';
import { ChartType } from '../../interfaces';
const meta = {
title: 'Base/Charts/OverflowButton',

View File

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

View File

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

View File

@ -16,7 +16,6 @@ export const BusterMetricChart: React.FC<BusterMetricChartProps> = React.memo(
metricSubHeader,
metricValueAggregate,
data,
isDarkMode,
animate,
columnLabelFormats,
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';
import { Button } from 'antd';
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 {
children: ReactNode;
}
@ -31,26 +37,30 @@ export class GlobalErrorComponent extends Component<Props, State> {
<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"
role="alert">
<div className="-mt-4 max-w-md rounded-lg border border-gray-200 bg-white/90 p-10">
<div className="mb-8 flex flex-col gap-4">
<Title className="animate-fade-in text-center" level={1}>
Looks like we hit an error! 😅
</Title>
<Card>
<CardContent>
<div className="flex flex-col gap-4">
<h1 className="animate-fade-in text-2xl font-semibold">
Looks like we hit an error! 😅
</h1>
<Title level={5} className="animate-slide-up m-0! text-gray-600!">
Don&apos;t worry, it&apos;s not you - it&apos;s us!
</Title>
<Title level={5} className="animate-slide-up m-0! text-gray-500!">
If this error persists, please contact Buster support!
</Title>
</div>
<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!
</h5>
<h5 className="animate-slide-up m-0 text-base font-medium text-gray-500">
If this error persists, please contact Buster support!
</h5>
</div>
</CardContent>
<a href="/" className="block">
<Button type="primary" block size="large" className="w-full">
Take Me Home 🏠
</Button>
</a>
</div>
<CardFooter className="w-full pt-0">
<a href="/" className="w-full">
<Button variant="black" block size="tall">
Take Me Home 🏠
</Button>
</a>
</CardFooter>
</Card>
</div>
);
}

View File

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

View File

@ -3,7 +3,7 @@
import React from 'react';
import { useTheme } from 'next-themes';
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>;
@ -16,7 +16,9 @@ const Toaster = ({ ...props }: ToasterProps) => {
expand={true}
visibleToasts={5}
icons={{
success: <CircleCheck />
success: <CircleCheck />,
error: <CircleXmark />,
warning: <CircleWarning />
}}
swipeDirections={['right']}
theme={theme as ToasterProps['theme']}
@ -24,7 +26,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
toastOptions={{
classNames: {
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',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-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';
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">
<BusterUserAvatar image={image || undefined} name={name} size={18} />
<BusterUserAvatar image={image} name={name} size={18} />
</div>
));
OwnerCell.displayName = 'OwnerCell';