app selec tmultiple

This commit is contained in:
Nate Kelley 2025-03-03 15:42:29 -07:00
parent ebec5d45fa
commit 1a392db5bf
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
25 changed files with 106 additions and 86 deletions

View File

@ -8,4 +8,4 @@ alwaysApply: false
- If a new story is made in the controller directory, I would like it title "Controllers/{controller name or parent controller name}/{componentName}"
- If a new story is found in the features directory, I would like it titled "Features/{featureName or parent feature name}/{componentName}"
- onClick events (or similar) should be mocked with fn() from the storybook test package
- If I ever need to mock an endpoint you should use msw, All endpoints should be appended with the NEXT_PUBLIC_API_URL from env. For example: http.get(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/datasets`, () => {
- If I ever need to mock an endpoint you should use msw. Use MSW for mocking api calls. All endpoints should be appended with the BASE_URL For example: http.get(`BASE_URL`, () => {}). The BASE_URL is found in [config.ts](mdc:src/api/buster_rest/config.ts)

1
web/package-lock.json generated
View File

@ -2467,7 +2467,6 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {

View File

@ -1 +1,2 @@
export const NEXT_SB_ACCESS_TOKEN = 'next-sb-access-token';
export const BASE_URL = `${process.env.NEXT_PUBLIC_API_URL}/api/v1`;

View File

@ -1,6 +1,5 @@
import { createInstance } from '../createInstance';
export const BASE_URL = `${process.env.NEXT_PUBLIC_API_URL}/api/v1`;
import { BASE_URL } from './config';
const mainApi = createInstance(BASE_URL);
export default mainApi;

View File

@ -1,6 +1,7 @@
import { useGetDatasets } from '@/api/buster_rest';
import { SelectMultiple } from '@/components/ui/select/SelectMultiple';
import { Select, SelectItem } from '@/components/ui/select/Select';
import { useMemoizedFn } from 'ahooks';
import { Select } from 'antd';
import React, { useMemo, useState } from 'react';
export const SelectedDatasetInput: React.FC<{
@ -15,7 +16,12 @@ export const SelectedDatasetInput: React.FC<{
onSetDatasetId(value);
});
const options = useMemo(() => {
const onChangeSinglePreflight = useMemoizedFn((value: string) => {
const newValue = [value];
onChangePreflight(newValue);
});
const options: SelectItem[] = useMemo(() => {
return datasets?.map((dataset) => ({
label: dataset.name,
value: dataset.id
@ -25,28 +31,23 @@ export const SelectedDatasetInput: React.FC<{
if (mode === 'single') {
return (
<Select
options={options}
onChange={onChangePreflight}
value={value}
items={options}
onChange={onChangeSinglePreflight}
value={value[0]}
placeholder="Select a dataset"
loading={!isFetched}
className="w-full"
disabled={!isFetched}
/>
);
}
// return (
// <AppSelectMultiple
// options={options}
// onChange={onChangePreflight}
// value={value}
// placeholder="Select a dataset"
// loading={!isFetched}
// className="w-full"
// />
// );
return <></>;
return (
<SelectMultiple
placeholder="Select a dataset"
items={options}
onChange={onChangePreflight}
value={value}
/>
);
});
SelectedDatasetInput.displayName = 'SelectedDatasetInput';

View File

@ -14,9 +14,7 @@ const meta = {
status: VerificationStatus.notRequested,
id: 'metric-123',
isAdmin: false,
onVerify: fn(async (params) => {
return Promise.resolve();
}),
onVerify: fn(),
disabled: false
},
argTypes: {

View File

@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { NewTermModal } from './NewTermModal';
import { http, HttpResponse } from 'msw';
import { fn } from '@storybook/test';
import { BASE_URL } from '@/api/buster_rest/config';
interface TermRequestBody {
name: string;
@ -16,14 +17,14 @@ const meta = {
layout: 'centered',
msw: {
handlers: [
http.get(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/datasets`, () => {
http.get(`${BASE_URL}/datasets`, () => {
return HttpResponse.json([
{ id: '1', name: 'Customer Data' },
{ id: '2', name: 'Sales Data' },
{ id: '3', name: 'Product Analytics' }
]);
}),
http.post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/terms`, async ({ request }) => {
http.post(`${BASE_URL}/terms`, async ({ request }) => {
const body = (await request.json()) as TermRequestBody;
return HttpResponse.json({
success: true,

View File

@ -135,7 +135,7 @@ const DatasetListContainer: React.FC<{
<SelectMultiple
items={selectOptions}
disabled={!isFetched}
onSelect={onChange}
onChange={onChange}
placeholder="Select a dataset"
value={selectedDatasets}
/>

View File

@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { SaveResetFilePopup } from './SaveResetFilePopup';
import React from 'react';
import { Button } from '@/components/ui/buttons';
import { fn } from '@storybook/test';
const meta: Meta<typeof SaveResetFilePopup> = {
title: 'Features/Popups/SaveResetFilePopup',
@ -58,8 +59,8 @@ export const Default: Story = {
render: (args) => <PopupContainer {...args} />,
args: {
open: true,
onReset: () => console.log('Reset clicked'),
onSave: () => console.log('Save clicked')
onReset: fn(),
onSave: fn()
}
};
@ -67,7 +68,7 @@ export const Hidden: Story = {
render: (args) => <PopupContainer {...args} />,
args: {
open: false,
onReset: () => console.log('Reset clicked'),
onSave: () => console.log('Save clicked')
onReset: fn(),
onSave: fn()
}
};

View File

@ -21,7 +21,7 @@ export default meta;
type Story = StoryObj<typeof CodeCard>;
const sampleCode = `function helloWorld() {
console.log('Hello, World!');
console.swag('Hello, World!');
}`;
const longCode = `import React from 'react';
@ -29,7 +29,7 @@ import { Button } from '../buttons';
export const MyComponent = () => {
const handleClick = () => {
console.log('Button clicked!');
console.swag('Button clicked!');
};
return (

View File

@ -146,8 +146,8 @@ export const WithFocusEvent: Story = {
id: '7',
serieName: 'series7'
},
onClickItem: fn,
onFocusItem: fn,
onHoverItem: fn
onClickItem: fn(),
onFocusItem: fn(),
onHoverItem: fn()
}
};

View File

@ -45,8 +45,8 @@ const mockLegendItems = [
export const Default: Story = {
args: {
legendItems: mockLegendItems,
onClickItem: fn,
onFocusClick: fn
onClickItem: fn(),
onFocusClick: fn()
}
};

View File

@ -93,6 +93,8 @@ export const InputTextAreaButton = forwardRef<HTMLTextAreaElement, InputTextArea
}
);
InputTextAreaButton.displayName = 'InputTextAreaButton';
const SubmitButton: React.FC<{
loading: boolean;
disabled?: boolean;

View File

@ -59,8 +59,7 @@ const PageLayout: React.FC<
className={cn(
'h-screen w-full overflow-hidden',
'py-2',
floating && 'pr-2 pl-2',
floating && hasSidebar ? 'pr-2' : 'pr-2 pl-2',
className
)}>
<div className={cn('bg-background h-full overflow-hidden', floating && 'rounded border')}>

View File

@ -3,6 +3,7 @@ import { AppModal } from './AppModal';
import { Button } from '../buttons/Button';
import React from 'react';
import { ModalProps } from './AppModal';
import { fn } from '@storybook/test';
const meta: Meta<typeof AppModal> = {
title: 'UI/Modal/AppModal',
component: AppModal,
@ -84,11 +85,11 @@ export const Default: Story = {
footer: {
primaryButton: {
text: 'Confirm',
onClick: () => console.log('Primary button clicked')
onClick: fn()
},
secondaryButton: {
text: 'Cancel',
onClick: () => console.log('Secondary button clicked')
onClick: fn()
}
},
width: 600,
@ -110,7 +111,7 @@ export const WithoutDescription: Story = {
footer: {
primaryButton: {
text: 'OK',
onClick: () => console.log('OK clicked')
onClick: fn()
}
},
width: 500,
@ -132,12 +133,12 @@ export const LoadingState: Story = {
footer: {
primaryButton: {
text: 'Submit',
onClick: () => console.log('Submit clicked'),
onClick: fn(),
loading: true
},
secondaryButton: {
text: 'Cancel',
onClick: () => console.log('Cancel clicked'),
onClick: fn(),
disabled: true
}
},
@ -160,11 +161,11 @@ export const CustomWidth: Story = {
footer: {
primaryButton: {
text: 'Save',
onClick: () => console.log('Save clicked')
onClick: fn()
},
secondaryButton: {
text: 'Discard',
onClick: () => console.log('Discard clicked')
onClick: fn()
}
},
width: 300,
@ -191,11 +192,11 @@ export const WithCustomFooterLeft: Story = {
left: <span className="text-sm text-gray-500">Additional footer information</span>,
primaryButton: {
text: 'Continue',
onClick: () => console.log('Continue clicked')
onClick: fn()
},
secondaryButton: {
text: 'Back',
onClick: () => console.log('Back clicked')
onClick: fn()
}
},
width: 600,

View File

@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Select } from './Select';
import { User, MapSettings, Mailbox } from '../icons';
import { fn } from '@storybook/test';
const meta: Meta<typeof Select> = {
title: 'UI/Select/Select',
@ -8,6 +9,9 @@ const meta: Meta<typeof Select> = {
parameters: {
layout: 'centered'
},
args: {
onChange: fn()
},
tags: ['autodocs']
};

View File

@ -26,19 +26,34 @@ export interface SelectItem {
export interface SelectProps {
items: SelectItem[] | SelectItemGroup[];
disabled?: boolean;
onSelect?: (value: string) => void;
onChange: (value: string) => void;
placeholder?: string;
value?: string;
onOpenChange?: (open: boolean) => void;
open?: boolean;
showIndex?: boolean;
className?: string;
}
export const Select: React.FC<SelectProps> = React.memo(
({ items, showIndex, disabled, onSelect, placeholder, value, onOpenChange, open }) => {
({
items,
showIndex,
disabled,
onChange,
placeholder,
value,
onOpenChange,
open,
className = ''
}) => {
return (
<SelectBase disabled={disabled} onOpenChange={onOpenChange} open={open}>
<SelectTrigger>
<SelectBase
disabled={disabled}
onOpenChange={onOpenChange}
open={open}
onValueChange={onChange}>
<SelectTrigger className={className}>
<SelectValue placeholder={placeholder} defaultValue={value} />
</SelectTrigger>
<SelectContent>

View File

@ -13,7 +13,7 @@ const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
export const selectVariants = cva(
'flex w-full gap-x-1.5 transition-all duration-200 items-center justify-between rounded border px-3 py-1 text-sm focus:outline-none cursor-pointer disabled:cursor-not-allowed disabled:opacity-60 [&>span]:line-clamp-1',
'flex w-full gap-x-1.5 transition-colors transition-border duration-200 items-center justify-between rounded border px-3 py-1 text-sm focus:outline-none cursor-pointer disabled:cursor-not-allowed disabled:opacity-60 [&>span]:line-clamp-1',
{
variants: {
variant: {

View File

@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { SelectMultiple } from './SelectMultiple';
import { useState } from 'react';
import { type SelectItem } from './Select';
import { fn } from '@storybook/test';
const meta = {
title: 'UI/Select/SelectMultiple',
@ -36,7 +37,7 @@ const SelectMultipleWithHooks = () => {
<div className="w-[300px]">
<SelectMultiple
items={items}
onSelect={handleSelect}
onChange={handleSelect}
placeholder="Select multiple options..."
value={value}
/>
@ -47,7 +48,7 @@ const SelectMultipleWithHooks = () => {
export const Default: Story = {
args: {
items: baseItems,
onSelect: () => {},
onChange: fn(),
value: [],
placeholder: 'Select multiple options...'
},
@ -58,7 +59,7 @@ export const WithPreselectedValues: Story = {
args: {
items: baseItems,
value: ['1', '2'],
onSelect: () => {},
onChange: fn(),
placeholder: 'Select multiple options...'
},
render: (args) => (
@ -71,7 +72,7 @@ export const WithPreselectedValues: Story = {
export const Empty: Story = {
args: {
items: baseItems,
onSelect: () => {},
onChange: fn(),
placeholder: 'Select multiple options...',
value: []
},
@ -86,7 +87,7 @@ export const FullySelected: Story = {
args: {
items: baseItems,
value: baseItems.map((item) => item.value),
onSelect: () => {},
onChange: fn(),
placeholder: 'Select multiple options...'
},
render: (args) => (
@ -100,7 +101,7 @@ export const CustomWidth: Story = {
args: {
items: baseItems,
value: ['1'],
onSelect: () => {},
onChange: fn(),
placeholder: 'Select multiple options...'
},
render: (args) => (

View File

@ -11,7 +11,7 @@ import { InputTag } from '../inputs/InputTag';
interface SelectMultipleProps extends VariantProps<typeof selectVariants> {
items: SelectItem[];
onSelect: (item: string[]) => void;
onChange: (item: string[]) => void;
className?: string;
placeholder?: string;
value: string[];
@ -21,7 +21,7 @@ interface SelectMultipleProps extends VariantProps<typeof selectVariants> {
export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
({
items: itemsProp,
onSelect,
onChange,
className,
placeholder = 'Select items...',
size = 'default',
@ -40,7 +40,7 @@ export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
const newSelected = itemsProp
.filter((item) => item.value !== valueToRemove && selectedRecord[item.value])
.map((item) => item.value);
onSelect(newSelected);
onChange(newSelected);
};
const handleSelect = useMemoizedFn((itemId: string) => {
@ -52,7 +52,7 @@ export const SelectMultiple: React.FC<SelectMultipleProps> = React.memo(
const newSelected = itemsProp
.filter((item) => selectedRecord[item.value])
.map((item) => item.value);
onSelect([...newSelected, item.value]);
onChange([...newSelected, item.value]);
}
}
});

View File

@ -6,7 +6,7 @@ const meta: Meta<typeof AppCodeBlock> = {
component: AppCodeBlock,
tags: ['autodocs'],
args: {
children: 'const greeting = "Hello, world!";\nconsole.log(greeting);',
children: 'const greeting = "Hello, world!";\nconsole.swag(greeting);',
language: 'javascript'
},
argTypes: {
@ -73,7 +73,7 @@ export const WithCustomTitle: Story = {
language: 'javascript',
title: 'Example Code',
children:
'function calculateSum(a, b) {\n return a + b;\n}\n\nconst result = calculateSum(5, 10);\nconsole.log(`The sum is ${result}`);'
'function calculateSum(a, b) {\n return a + b;\n}\n\nconst result = calculateSum(5, 10);\nconsole.swag(`The sum is ${result}`);'
}
};

View File

@ -81,7 +81,7 @@ export const WithCodeBlocks: Story = {
\`\`\`javascript
function helloWorld() {
console.log('Hello, world!');
console.swag('Hello, world!');
}
\`\`\`

View File

@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { EditableTitle } from './EditableTitle';
import React from 'react';
import { fn } from '@storybook/test';
const meta: Meta<typeof EditableTitle> = {
title: 'UI/Typography/EditableTitle',
@ -67,8 +68,8 @@ export const Default: Story = {
args: {
children: 'Editable Title',
level: 4,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing),
onChange: fn(),
onEdit: fn(),
placeholder: 'Enter a title'
}
};
@ -78,8 +79,8 @@ export const Level1: Story = {
args: {
children: 'Large Heading',
level: 1,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};
@ -88,8 +89,8 @@ export const Level2: Story = {
args: {
children: 'Medium Heading',
level: 2,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};
@ -98,8 +99,8 @@ export const Level3: Story = {
args: {
children: 'Small Heading',
level: 3,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};
@ -109,8 +110,8 @@ export const Disabled: Story = {
children: 'Non-editable Title',
level: 4,
disabled: true,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};
@ -120,8 +121,8 @@ export const WithPlaceholder: Story = {
children: '',
level: 4,
placeholder: 'Enter your title here...',
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};
@ -131,7 +132,7 @@ export const InitiallyEditing: Story = {
children: 'Initially in Edit Mode',
level: 4,
editing: true,
onChange: (value) => console.log('Value changed:', value),
onEdit: (editing) => console.log('Editing state:', editing)
onChange: fn(),
onEdit: fn()
}
};

View File

@ -20,7 +20,6 @@ export const useBusterNewChat = () => {
} = useChatStreamMessage();
const onSelectSearchAsset = useMemoizedFn(async (asset: BusterSearchResult) => {
console.log('select search asset');
await new Promise((resolve) => setTimeout(resolve, 1000));
});

View File

@ -6,8 +6,6 @@
@import 'react-data-grid/lib/styles.css';
@import '../components/ui/layouts/AppSplitter/SplitPane/themes/default'; //TODO check if we can remove this
// @import 'antd/dist/reset.css';
input {
font-family:
var(--font-app),