From 9f1c75dc5dfeba728b20eb1cb40ba87877b75a57 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 27 Feb 2025 08:38:44 -0700 Subject: [PATCH] update rules around ui --- .../rules/components_features_rules.mdc | 8 +- web/.cursor/rules/components_ui_rules.mdc | 37 +++------ web/.cursor/rules/components_ui_stories.mdc | 3 +- web/.cursor/rules/typescript_rules.mdc | 9 +-- .../[datasetId]/editor/EditorContent.tsx | 4 +- .../AppSplitter/AppSplitter.stories.tsx | 2 +- .../layout/AppVerticalCodeSplitter/index.ts | 1 - .../AppVerticalSplitterWithGap.stories.tsx | 75 +++++++++++++++++++ .../AppVerticalSplitterWithGap.tsx} | 18 +++-- .../DataContainer.tsx | 0 .../SQLContainer.tsx | 8 +- .../AppVerticalSplitterWithGap/index.ts | 1 + .../MetricViewResultsController.tsx | 4 +- 13 files changed, 118 insertions(+), 52 deletions(-) delete mode 100644 web/src/components/ui/layout/AppVerticalCodeSplitter/index.ts create mode 100644 web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.stories.tsx rename web/src/components/ui/layout/{AppVerticalCodeSplitter/AppVerticalCodeSplitter.tsx => AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.tsx} (76%) rename web/src/components/ui/layout/{AppVerticalCodeSplitter => AppVerticalSplitterWithGap}/DataContainer.tsx (100%) rename web/src/components/ui/layout/{AppVerticalCodeSplitter => AppVerticalSplitterWithGap}/SQLContainer.tsx (93%) create mode 100644 web/src/components/ui/layout/AppVerticalSplitterWithGap/index.ts diff --git a/web/.cursor/rules/components_features_rules.mdc b/web/.cursor/rules/components_features_rules.mdc index 833193e63..d204ad31c 100644 --- a/web/.cursor/rules/components_features_rules.mdc +++ b/web/.cursor/rules/components_features_rules.mdc @@ -1,6 +1,7 @@ --- description: Rules for the components ui directory globs: src/components/features/**/* +alwaysApply: false --- # Feature Components Rules @@ -14,8 +15,8 @@ Each feature component should be organized in its own directory using the follow features/ └── ComponentName/ ├── ComponentName.tsx # Main component implementation - ├── ComponentName.types.ts # TypeScript interfaces and types - ├── ComponentName.test.tsx # Tests for the component + ├── ComponentName.test.tsx # Tests for the component (if asked for it) + ├── ComponentName.stories.tsx # Storybook stories for the component └── index.ts # Exports the component and its types ``` @@ -26,11 +27,12 @@ features/ ## Component Guidelines 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 - Handle their own state management when necessary - Be fully typed using TypeScript - Include proper documentation and props interface + - Have a corresponding Storybook story showcasing different states and variations 2. Components should be: - Reusable across different parts of the application diff --git a/web/.cursor/rules/components_ui_rules.mdc b/web/.cursor/rules/components_ui_rules.mdc index 88bcecd52..f73724898 100644 --- a/web/.cursor/rules/components_ui_rules.mdc +++ b/web/.cursor/rules/components_ui_rules.mdc @@ -3,7 +3,6 @@ description: Rules for the components ui directory globs: src/components/ui/**/* alwaysApply: false --- - # Component Directory Structure and Organization ## 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 ## 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 - Examples: - `buttons/` - Button components @@ -21,16 +20,21 @@ alwaysApply: false - `card/` - Card and container components - `icons/` - Icon 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 - When a component needs to use another component, prefer relative imports - Example: ```typescript // 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 1. Primary Styling Method: @@ -38,33 +42,12 @@ alwaysApply: false - Class names should follow Tailwind's utility-first approach 2. Color Management: - - Always use `createStyles` from antd-style for color-related styling - - 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 + - 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. ## Component Implementation - Use functional components exclusively - Implement proper TypeScript types for props and state - 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'; diff --git a/web/.cursor/rules/components_ui_stories.mdc b/web/.cursor/rules/components_ui_stories.mdc index 6ecb59b67..07aa26ade 100644 --- a/web/.cursor/rules/components_ui_stories.mdc +++ b/web/.cursor/rules/components_ui_stories.mdc @@ -1,5 +1,6 @@ --- description: Rules for new stories globs: src/components/**/*.stories.tsx +alwaysApply: false --- -All new stories title with "Base/{componentName}" unless specified otherwise. \ No newline at end of file +All new stories title with "UI/directory/{componentName}" unless specified otherwise. \ No newline at end of file diff --git a/web/.cursor/rules/typescript_rules.mdc b/web/.cursor/rules/typescript_rules.mdc index 71c52df53..623df954c 100644 --- a/web/.cursor/rules/typescript_rules.mdc +++ b/web/.cursor/rules/typescript_rules.mdc @@ -1,6 +1,7 @@ --- description: Rules for the context directory globs: src/**/*.{ts,tsx} +alwaysApply: false --- # 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 ## Best Practices -- Try to use Ant Design componets (Button, Input, etc) over native HTML elements. -- 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 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 +- Try to use components defined in my @/components/ui folder. +- If we need to create custom elements, we should try to use tailwind to defined the component structure - Use React.memo() for performance optimization when appropriate - Prefer async/await over .then() for asynchronous operations ## ADDITIONAL PARAMS - If we need to use useRouter, if needs to be from next/navigation -- If I need to use text, I want to import the component from the @/components/text, If I need to use a title, I want to import the component from the @/components/text directory. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/web/src/app/app/datasets/[datasetId]/editor/EditorContent.tsx b/web/src/app/app/datasets/[datasetId]/editor/EditorContent.tsx index 11762f55f..75668e5a8 100644 --- a/web/src/app/app/datasets/[datasetId]/editor/EditorContent.tsx +++ b/web/src/app/app/datasets/[datasetId]/editor/EditorContent.tsx @@ -10,7 +10,7 @@ import { runSQL } from '@/api/buster_rest'; import type { RustApiError } from '@/api/buster_rest/errors'; import isEmpty from 'lodash/isEmpty'; 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'; export const EditorContent: React.FC<{ @@ -73,7 +73,7 @@ export const EditorContent: React.FC<{ <EditorContainerSubHeader selectedApp={selectedApp} setSelectedApp={setSelectedApp} /> <div className={cx('h-full w-full overflow-hidden p-5', styles.container)}> {selectedApp === EditorApps.PREVIEW && ( - <AppVerticalCodeSplitter + <AppVerticalSplitterWithGap autoSaveId="dataset-editor" ref={splitterRef} sql={sql} diff --git a/web/src/components/ui/layout/AppSplitter/AppSplitter.stories.tsx b/web/src/components/ui/layout/AppSplitter/AppSplitter.stories.tsx index bde00292d..162ac4427 100644 --- a/web/src/components/ui/layout/AppSplitter/AppSplitter.stories.tsx +++ b/web/src/components/ui/layout/AppSplitter/AppSplitter.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { AppSplitter } from './AppSplitter'; const meta = { - title: 'Base/Layout/AppSplitter', + title: 'Base/Layouts/AppSplitter', component: AppSplitter, parameters: { layout: 'fullscreen' diff --git a/web/src/components/ui/layout/AppVerticalCodeSplitter/index.ts b/web/src/components/ui/layout/AppVerticalCodeSplitter/index.ts deleted file mode 100644 index cd6746631..000000000 --- a/web/src/components/ui/layout/AppVerticalCodeSplitter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './AppVerticalCodeSplitter'; diff --git a/web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.stories.tsx b/web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.stories.tsx new file mode 100644 index 000000000..270d6fa83 --- /dev/null +++ b/web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.stories.tsx @@ -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 + } +}; diff --git a/web/src/components/ui/layout/AppVerticalCodeSplitter/AppVerticalCodeSplitter.tsx b/web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.tsx similarity index 76% rename from web/src/components/ui/layout/AppVerticalCodeSplitter/AppVerticalCodeSplitter.tsx rename to web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.tsx index fc08045d6..96ec5e9d2 100644 --- a/web/src/components/ui/layout/AppVerticalCodeSplitter/AppVerticalCodeSplitter.tsx +++ b/web/src/components/ui/layout/AppVerticalSplitterWithGap/AppVerticalSplitterWithGap.tsx @@ -4,7 +4,7 @@ import { SQLContainer } from './SQLContainer'; import { DataContainer } from './DataContainer'; import type { IDataResult } from '@/api/asset_interfaces'; -export interface AppVerticalCodeSplitterProps { +export interface AppVerticalSplitterWithGapProps { sql: string; setSQL: (sql: string) => void; runSQLError: string | null; @@ -16,9 +16,13 @@ export interface AppVerticalCodeSplitterProps { topHidden?: boolean; onSaveSQL?: () => Promise<void>; disabledSave?: boolean; + gapAmount?: number; } -export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCodeSplitterProps>( +export const AppVerticalSplitterWithGap = forwardRef< + AppSplitterRef, + AppVerticalSplitterWithGapProps +>( ( { sql, @@ -31,12 +35,14 @@ export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCod defaultLayout, autoSaveId, disabledSave = false, - topHidden = false + topHidden = false, + gapAmount = 3 }, ref ) => { - const sqlContainerClassName = !topHidden ? 'mb-3' : ''; - const dataContainerClassName = !topHidden ? 'mt-3' : ''; + //tailwind might not like this, but yolo + const sqlContainerClassName = !topHidden ? `mb-${gapAmount}` : ''; + const dataContainerClassName = !topHidden ? `mt-${gapAmount}` : ''; return ( <AppSplitter @@ -71,4 +77,4 @@ export const AppVerticalCodeSplitter = forwardRef<AppSplitterRef, AppVerticalCod } ); -AppVerticalCodeSplitter.displayName = 'AppVerticalCodeSplitter'; +AppVerticalSplitterWithGap.displayName = 'AppVerticalSplitterWithGap'; diff --git a/web/src/components/ui/layout/AppVerticalCodeSplitter/DataContainer.tsx b/web/src/components/ui/layout/AppVerticalSplitterWithGap/DataContainer.tsx similarity index 100% rename from web/src/components/ui/layout/AppVerticalCodeSplitter/DataContainer.tsx rename to web/src/components/ui/layout/AppVerticalSplitterWithGap/DataContainer.tsx diff --git a/web/src/components/ui/layout/AppVerticalCodeSplitter/SQLContainer.tsx b/web/src/components/ui/layout/AppVerticalSplitterWithGap/SQLContainer.tsx similarity index 93% rename from web/src/components/ui/layout/AppVerticalCodeSplitter/SQLContainer.tsx rename to web/src/components/ui/layout/AppVerticalSplitterWithGap/SQLContainer.tsx index 3fbd71fb5..04d931502 100644 --- a/web/src/components/ui/layout/AppVerticalCodeSplitter/SQLContainer.tsx +++ b/web/src/components/ui/layout/AppVerticalSplitterWithGap/SQLContainer.tsx @@ -6,15 +6,15 @@ import { Button, Divider } from 'antd'; import { createStyles } from 'antd-style'; import React, { useEffect, useMemo, useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import type { AppVerticalCodeSplitterProps } from './AppVerticalCodeSplitter'; +import type { AppVerticalSplitterWithGapProps } from './AppVerticalSplitterWithGap'; export const SQLContainer: React.FC<{ className?: string; sql: string | undefined; setDatasetSQL: (sql: string) => void; onRunQuery: () => Promise<void>; - onSaveSQL?: AppVerticalCodeSplitterProps['onSaveSQL']; - disabledSave?: AppVerticalCodeSplitterProps['disabledSave']; + onSaveSQL?: AppVerticalSplitterWithGapProps['onSaveSQL']; + disabledSave?: AppVerticalSplitterWithGapProps['disabledSave']; error?: string | null; }> = React.memo( ({ disabledSave, className = '', sql, setDatasetSQL, onRunQuery, onSaveSQL, error }) => { @@ -101,7 +101,7 @@ const ErrorContainer: React.FC<{ initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, 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 gap-2"> <AppMaterialIcons icon="error" /> diff --git a/web/src/components/ui/layout/AppVerticalSplitterWithGap/index.ts b/web/src/components/ui/layout/AppVerticalSplitterWithGap/index.ts new file mode 100644 index 000000000..9bd5583ce --- /dev/null +++ b/web/src/components/ui/layout/AppVerticalSplitterWithGap/index.ts @@ -0,0 +1 @@ +export * from './AppVerticalSplitterWithGap'; diff --git a/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx b/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx index e063bfcf8..92278f7bc 100644 --- a/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx +++ b/web/src/controllers/MetricController/MetricViewResults/MetricViewResultsController.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo } from 'react'; import type { MetricViewProps } from '../config'; 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 { IDataResult } from '@/api/asset_interfaces'; import { useMetricLayout } from '../useMetricLayout'; @@ -82,7 +82,7 @@ export const MetricViewResults: React.FC<MetricViewProps> = React.memo(({ metric return ( <div ref={containerRef} className="h-full w-full p-3"> - <AppVerticalCodeSplitter + <AppVerticalSplitterWithGap ref={appSplitterRef} autoSaveId={autoSaveId} sql={sql}