mirror of https://github.com/buster-so/buster.git
update rules around ui
This commit is contained in:
parent
2b100ed60b
commit
9f1c75dc5d
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
description: Rules for the components ui directory
|
description: Rules for the components ui directory
|
||||||
globs: src/components/features/**/*
|
globs: src/components/features/**/*
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
# Feature Components Rules
|
# Feature Components Rules
|
||||||
|
|
||||||
|
@ -14,8 +15,8 @@ Each feature component should be organized in its own directory using the follow
|
||||||
features/
|
features/
|
||||||
└── ComponentName/
|
└── ComponentName/
|
||||||
├── ComponentName.tsx # Main component implementation
|
├── ComponentName.tsx # Main component implementation
|
||||||
├── ComponentName.types.ts # TypeScript interfaces and types
|
├── ComponentName.test.tsx # Tests for the component (if asked for it)
|
||||||
├── ComponentName.test.tsx # Tests for the component
|
├── ComponentName.stories.tsx # Storybook stories for the component
|
||||||
└── index.ts # Exports the component and its types
|
└── index.ts # Exports the component and its types
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -26,11 +27,12 @@ features/
|
||||||
|
|
||||||
## Component Guidelines
|
## Component Guidelines
|
||||||
1. Feature components should:
|
1. Feature components should:
|
||||||
- Import basic UI components from `../ui/`
|
- Import basic UI components from `../ui/{component_directory}/{component_name}`
|
||||||
- Be functional components using React hooks
|
- Be functional components using React hooks
|
||||||
- Handle their own state management when necessary
|
- Handle their own state management when necessary
|
||||||
- Be fully typed using TypeScript
|
- Be fully typed using TypeScript
|
||||||
- Include proper documentation and props interface
|
- Include proper documentation and props interface
|
||||||
|
- Have a corresponding Storybook story showcasing different states and variations
|
||||||
|
|
||||||
2. Components should be:
|
2. Components should be:
|
||||||
- Reusable across different parts of the application
|
- Reusable across different parts of the application
|
||||||
|
|
|
@ -3,7 +3,6 @@ description: Rules for the components ui directory
|
||||||
globs: src/components/ui/**/*
|
globs: src/components/ui/**/*
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Component Directory Structure and Organization
|
# Component Directory Structure and Organization
|
||||||
|
|
||||||
## Component Purpose
|
## Component Purpose
|
||||||
|
@ -13,7 +12,7 @@ alwaysApply: false
|
||||||
- The goal is to maintain a library of simple, pure components that can be easily reused across features
|
- The goal is to maintain a library of simple, pure components that can be easily reused across features
|
||||||
|
|
||||||
## Directory Organization
|
## Directory Organization
|
||||||
- Each directory within `src/components` should represent a specific category of components
|
- Each directory within `src/components/ui` should represent a specific category of components
|
||||||
- Directory names should clearly indicate the type of components contained within
|
- Directory names should clearly indicate the type of components contained within
|
||||||
- Examples:
|
- Examples:
|
||||||
- `buttons/` - Button components
|
- `buttons/` - Button components
|
||||||
|
@ -21,16 +20,21 @@ alwaysApply: false
|
||||||
- `card/` - Card and container components
|
- `card/` - Card and container components
|
||||||
- `icons/` - Icon components
|
- `icons/` - Icon components
|
||||||
- `layout/` - Layout-related components
|
- `layout/` - Layout-related components
|
||||||
- `text/` - Text and typography components
|
- `typography/` - Text and typography components
|
||||||
|
|
||||||
|
## Storybook
|
||||||
|
- Each component in this directory should have an assosciated storybook
|
||||||
|
- Each storybook story should try to include the args (props) that are assosciated with each component
|
||||||
|
- Storybook storys should be title UI/{directory}/{component_name}
|
||||||
|
|
||||||
## Component Dependencies and Imports
|
## Component Dependencies and Imports
|
||||||
- When a component needs to use another component, prefer relative imports
|
- When a component needs to use another component, prefer relative imports
|
||||||
- Example:
|
- Example:
|
||||||
```typescript
|
```typescript
|
||||||
// Good
|
// Good
|
||||||
import { IconButton } from '../buttons/ui/IconButton';
|
import { Button } from '../buttons/ui/buttons/Button';
|
||||||
```
|
```
|
||||||
- Exception: Only use absolute imports (@/) when the relative path would be overly complex (>3 levels deep)
|
- Exception: Only use absolute imports (@/) when the relative path would be overly complex (>4 levels deep)
|
||||||
|
|
||||||
## Styling Guidelines
|
## Styling Guidelines
|
||||||
1. Primary Styling Method:
|
1. Primary Styling Method:
|
||||||
|
@ -38,33 +42,12 @@ alwaysApply: false
|
||||||
- Class names should follow Tailwind's utility-first approach
|
- Class names should follow Tailwind's utility-first approach
|
||||||
|
|
||||||
2. Color Management:
|
2. Color Management:
|
||||||
- Always use `createStyles` from antd-style for color-related styling
|
- Always use `tailwind.css` colors for color-related styling. Only use the colors defined in [tailwind.css](mdc:src/styles/tailwind.css). Do not use the other generic tailwind colors.
|
||||||
- Access theme colors through the token system
|
|
||||||
- Example:
|
|
||||||
```typescript
|
|
||||||
const useStyles = createStyles(({ token, css }) => {
|
|
||||||
return {
|
|
||||||
container: css`
|
|
||||||
border: 0.5px solid ${token.colorBorderSecondary};
|
|
||||||
background: ${token.colorBgContainer};
|
|
||||||
`
|
|
||||||
};
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Border Rules:
|
|
||||||
- When using borders with createStyles, always use 0.5px as the border width
|
|
||||||
- Access border colors through the token system
|
|
||||||
|
|
||||||
## Component Implementation
|
## Component Implementation
|
||||||
- Use functional components exclusively
|
- Use functional components exclusively
|
||||||
- Implement proper TypeScript types for props and state
|
- Implement proper TypeScript types for props and state
|
||||||
- Use React.memo() for performance optimization when appropriate
|
- Use React.memo() for performance optimization when appropriate
|
||||||
- Import text elements from dedicated components:
|
|
||||||
```typescript
|
|
||||||
import { Text } from '@/components/ui';
|
|
||||||
import { Title } from '@/components/ui';
|
|
||||||
```
|
|
||||||
|
|
||||||
##
|
##
|
||||||
I have a helper called `cn`. This is how I do a tailwind merge and classnames concatination. This is found in import { cn } from '@/lib/classMerge';
|
I have a helper called `cn`. This is how I do a tailwind merge and classnames concatination. This is found in import { cn } from '@/lib/classMerge';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
description: Rules for new stories
|
description: Rules for new stories
|
||||||
globs: src/components/**/*.stories.tsx
|
globs: src/components/**/*.stories.tsx
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
All new stories title with "Base/{componentName}" unless specified otherwise.
|
All new stories title with "UI/directory/{componentName}" unless specified otherwise.
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
description: Rules for the context directory
|
description: Rules for the context directory
|
||||||
globs: src/**/*.{ts,tsx}
|
globs: src/**/*.{ts,tsx}
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project: React TypeScript Application
|
# Project: React TypeScript Application
|
||||||
|
@ -13,14 +14,12 @@ You are a TypeScript expert with deep knowledge of the language's features and b
|
||||||
- Use proper TypeScript types for all variables and functions
|
- Use proper TypeScript types for all variables and functions
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
- Try to use Ant Design componets (Button, Input, etc) over native HTML elements.
|
- Try to use components defined in my @/components/ui folder.
|
||||||
- If we need to create custom elements like cards or containers with border colors, use createStyles from antd-style and use the token to get the exact color
|
- If we need to create custom elements, we should try to use tailwind to defined the component structure
|
||||||
- If newly defined element has a border, it should be 0.5px
|
|
||||||
- If I am ever instead createStyles function from antd-style AND I am using a border, it should be 0.5px
|
|
||||||
- Use React.memo() for performance optimization when appropriate
|
- Use React.memo() for performance optimization when appropriate
|
||||||
- Prefer async/await over .then() for asynchronous operations
|
- Prefer async/await over .then() for asynchronous operations
|
||||||
|
|
||||||
|
|
||||||
## ADDITIONAL PARAMS
|
## ADDITIONAL PARAMS
|
||||||
- If we need to use useRouter, if needs to be from next/navigation
|
- If we need to use useRouter, if needs to be from next/navigation
|
||||||
- If I need to use text, I want to import the <Text> component from the @/components/text, If I need to use a title, I want to import the <Title /> component from the @/components/text directory.
|
- If I need to use text, I want to import the <Text> component from the @/components/typography, If I need to use a title, I want to import the <Title /> component from the @/components/typography directory.
|
|
@ -10,7 +10,7 @@ import { runSQL } from '@/api/buster_rest';
|
||||||
import type { RustApiError } from '@/api/buster_rest/errors';
|
import type { RustApiError } from '@/api/buster_rest/errors';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import type { AppSplitterRef } from '@/components/ui/layout/AppSplitter';
|
import type { AppSplitterRef } from '@/components/ui/layout/AppSplitter';
|
||||||
import { AppVerticalCodeSplitter } from '@/components/ui/layout/AppVerticalCodeSplitter';
|
import { AppVerticalSplitterWithGap } from '@/components/ui/layout/AppVerticalSplitterWithGap';
|
||||||
import { useDatasetPageContextSelector } from '../_DatasetsLayout/DatasetPageContext';
|
import { useDatasetPageContextSelector } from '../_DatasetsLayout/DatasetPageContext';
|
||||||
|
|
||||||
export const EditorContent: React.FC<{
|
export const EditorContent: React.FC<{
|
||||||
|
@ -73,7 +73,7 @@ export const EditorContent: React.FC<{
|
||||||
<EditorContainerSubHeader selectedApp={selectedApp} setSelectedApp={setSelectedApp} />
|
<EditorContainerSubHeader selectedApp={selectedApp} setSelectedApp={setSelectedApp} />
|
||||||
<div className={cx('h-full w-full overflow-hidden p-5', styles.container)}>
|
<div className={cx('h-full w-full overflow-hidden p-5', styles.container)}>
|
||||||
{selectedApp === EditorApps.PREVIEW && (
|
{selectedApp === EditorApps.PREVIEW && (
|
||||||
<AppVerticalCodeSplitter
|
<AppVerticalSplitterWithGap
|
||||||
autoSaveId="dataset-editor"
|
autoSaveId="dataset-editor"
|
||||||
ref={splitterRef}
|
ref={splitterRef}
|
||||||
sql={sql}
|
sql={sql}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { AppSplitter } from './AppSplitter';
|
import { AppSplitter } from './AppSplitter';
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: 'Base/Layout/AppSplitter',
|
title: 'Base/Layouts/AppSplitter',
|
||||||
component: AppSplitter,
|
component: AppSplitter,
|
||||||
parameters: {
|
parameters: {
|
||||||
layout: 'fullscreen'
|
layout: 'fullscreen'
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './AppVerticalCodeSplitter';
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { AppVerticalSplitterWithGap } from './AppVerticalSplitterWithGap';
|
||||||
|
|
||||||
|
const meta: Meta<typeof AppVerticalSplitterWithGap> = {
|
||||||
|
title: 'Base/Layouts/AppVerticalSplitterWithGap',
|
||||||
|
component: AppVerticalSplitterWithGap,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen'
|
||||||
|
},
|
||||||
|
tags: ['autodocs']
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof AppVerticalSplitterWithGap>;
|
||||||
|
|
||||||
|
const mockData: Record<string, string | number | null>[] = [
|
||||||
|
{ id: 1, name: 'Sample Data 1' },
|
||||||
|
{ id: 2, name: 'Sample Data 2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
sql: 'SELECT * FROM sample_table',
|
||||||
|
setSQL: (sql: string) => console.log('SQL changed:', sql),
|
||||||
|
runSQLError: null,
|
||||||
|
onRunQuery: async () => console.log('Running query...'),
|
||||||
|
data: mockData,
|
||||||
|
fetchingData: false,
|
||||||
|
defaultLayout: ['50%', '50%'],
|
||||||
|
autoSaveId: 'default-splitter'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithError: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
runSQLError: 'Invalid SQL syntax'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
fetchingData: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TopHidden: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
topHidden: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomGap: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
gapAmount: 6
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithSaveButton: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
onSaveSQL: async () => console.log('Saving SQL...')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisabledSave: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
onSaveSQL: async () => console.log('Saving SQL...'),
|
||||||
|
disabledSave: true
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,7 +4,7 @@ import { SQLContainer } from './SQLContainer';
|
||||||
import { DataContainer } from './DataContainer';
|
import { DataContainer } from './DataContainer';
|
||||||
import type { IDataResult } from '@/api/asset_interfaces';
|
import type { IDataResult } from '@/api/asset_interfaces';
|
||||||
|
|
||||||
export interface AppVerticalCodeSplitterProps {
|
export interface AppVerticalSplitterWithGapProps {
|
||||||
sql: string;
|
sql: string;
|
||||||
setSQL: (sql: string) => void;
|
setSQL: (sql: string) => void;
|
||||||
runSQLError: string | null;
|
runSQLError: string | null;
|
||||||
|
@ -16,9 +16,13 @@ export interface AppVerticalCodeSplitterProps {
|
||||||
topHidden?: boolean;
|
topHidden?: boolean;
|
||||||
onSaveSQL?: () => Promise<void>;
|
onSaveSQL?: () => Promise<void>;
|
||||||
disabledSave?: boolean;
|
disabledSave?: boolean;
|
||||||
|
gapAmount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCodeSplitterProps>(
|
export const AppVerticalSplitterWithGap = forwardRef<
|
||||||
|
AppSplitterRef,
|
||||||
|
AppVerticalSplitterWithGapProps
|
||||||
|
>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
sql,
|
sql,
|
||||||
|
@ -31,12 +35,14 @@ export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCod
|
||||||
defaultLayout,
|
defaultLayout,
|
||||||
autoSaveId,
|
autoSaveId,
|
||||||
disabledSave = false,
|
disabledSave = false,
|
||||||
topHidden = false
|
topHidden = false,
|
||||||
|
gapAmount = 3
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const sqlContainerClassName = !topHidden ? 'mb-3' : '';
|
//tailwind might not like this, but yolo
|
||||||
const dataContainerClassName = !topHidden ? 'mt-3' : '';
|
const sqlContainerClassName = !topHidden ? `mb-${gapAmount}` : '';
|
||||||
|
const dataContainerClassName = !topHidden ? `mt-${gapAmount}` : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppSplitter
|
<AppSplitter
|
||||||
|
@ -71,4 +77,4 @@ export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCod
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
AppVerticalCodeSplitter.displayName = 'AppVerticalCodeSplitter';
|
AppVerticalSplitterWithGap.displayName = 'AppVerticalSplitterWithGap';
|
|
@ -6,15 +6,15 @@ import { Button, Divider } from 'antd';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles } from 'antd-style';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { AppVerticalCodeSplitterProps } from './AppVerticalCodeSplitter';
|
import type { AppVerticalSplitterWithGapProps } from './AppVerticalSplitterWithGap';
|
||||||
|
|
||||||
export const SQLContainer: React.FC<{
|
export const SQLContainer: React.FC<{
|
||||||
className?: string;
|
className?: string;
|
||||||
sql: string | undefined;
|
sql: string | undefined;
|
||||||
setDatasetSQL: (sql: string) => void;
|
setDatasetSQL: (sql: string) => void;
|
||||||
onRunQuery: () => Promise<void>;
|
onRunQuery: () => Promise<void>;
|
||||||
onSaveSQL?: AppVerticalCodeSplitterProps['onSaveSQL'];
|
onSaveSQL?: AppVerticalSplitterWithGapProps['onSaveSQL'];
|
||||||
disabledSave?: AppVerticalCodeSplitterProps['disabledSave'];
|
disabledSave?: AppVerticalSplitterWithGapProps['disabledSave'];
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
}> = React.memo(
|
}> = React.memo(
|
||||||
({ disabledSave, className = '', sql, setDatasetSQL, onRunQuery, onSaveSQL, error }) => {
|
({ disabledSave, className = '', sql, setDatasetSQL, onRunQuery, onSaveSQL, error }) => {
|
||||||
|
@ -101,7 +101,7 @@ const ErrorContainer: React.FC<{
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: 0 }}
|
exit={{ opacity: 0, y: 0 }}
|
||||||
className={cx(styles.errorContainer, 'absolute bottom-full left-0 right-0 mx-4 mb-2')}>
|
className={cx(styles.errorContainer, 'absolute right-0 bottom-full left-0 mx-4 mb-2')}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AppMaterialIcons icon="error" />
|
<AppMaterialIcons icon="error" />
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './AppVerticalSplitterWithGap';
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import type { MetricViewProps } from '../config';
|
import type { MetricViewProps } from '../config';
|
||||||
import { useMetricIndividual } from '@/context/Metrics';
|
import { useMetricIndividual } from '@/context/Metrics';
|
||||||
import { AppVerticalCodeSplitter } from '@/components/ui/layout/AppVerticalCodeSplitter';
|
import { AppVerticalSplitterWithGap } from '@/components/ui/layout/AppVerticalSplitterWithGap';
|
||||||
import { useMemoizedFn, useUnmount } from 'ahooks';
|
import { useMemoizedFn, useUnmount } from 'ahooks';
|
||||||
import { IDataResult } from '@/api/asset_interfaces';
|
import { IDataResult } from '@/api/asset_interfaces';
|
||||||
import { useMetricLayout } from '../useMetricLayout';
|
import { useMetricLayout } from '../useMetricLayout';
|
||||||
|
@ -82,7 +82,7 @@ export const MetricViewResults: React.FC<MetricViewProps> = React.memo(({ metric
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="h-full w-full p-3">
|
<div ref={containerRef} className="h-full w-full p-3">
|
||||||
<AppVerticalCodeSplitter
|
<AppVerticalSplitterWithGap
|
||||||
ref={appSplitterRef}
|
ref={appSplitterRef}
|
||||||
autoSaveId={autoSaveId}
|
autoSaveId={autoSaveId}
|
||||||
sql={sql}
|
sql={sql}
|
||||||
|
|
Loading…
Reference in New Issue