mirror of
https://github.com/apache/superset.git
synced 2026-04-09 19:35:21 +00:00
259 lines
7.2 KiB
Markdown
259 lines
7.2 KiB
Markdown
---
|
|
title: Component Style Guidelines and Best Practices
|
|
sidebar_position: 1
|
|
---
|
|
|
|
<!--
|
|
Licensed to the Apache Software Foundation (ASF) under one
|
|
or more contributor license agreements. See the NOTICE file
|
|
distributed with this work for additional information
|
|
regarding copyright ownership. The ASF licenses this file
|
|
to you under the Apache License, Version 2.0 (the
|
|
"License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing,
|
|
software distributed under the License is distributed on an
|
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
KIND, either express or implied. See the License for the
|
|
specific language governing permissions and limitations
|
|
under the License.
|
|
-->
|
|
|
|
# Component Style Guidelines and Best Practices
|
|
|
|
This documentation illustrates how we approach component development in Superset and provides examples to help you in writing new components or updating existing ones by following our community-approved standards.
|
|
|
|
This guide is intended primarily for reusable components. Whenever possible, all new components should be designed with reusability in mind.
|
|
|
|
## General Guidelines
|
|
|
|
- We use [Ant Design](https://ant.design/) as our component library. Do not build a new component if Ant Design provides one but rather instead extend or customize what the library provides
|
|
- Always style your component using Emotion and always prefer the theme variables whenever applicable. See: [Emotion Styling Guidelines and Best Practices](./emotion-styling-guidelines)
|
|
- All components should be made to be reusable whenever possible
|
|
- All components should follow the structure and best practices as detailed below
|
|
|
|
## Directory and component structure
|
|
|
|
```
|
|
superset-frontend/src/components
|
|
{ComponentName}/
|
|
index.tsx
|
|
{ComponentName}.test.tsx
|
|
{ComponentName}.stories.tsx
|
|
```
|
|
|
|
**Components root directory:** Components that are meant to be re-used across different parts of the application should go in the `superset-frontend/src/components` directory. Components that are meant to be specific for a single part of the application should be located in the nearest directory where the component is used, for example, `superset-frontend/src/Explore/components`
|
|
|
|
**Exporting the component:** All components within the `superset-frontend/src/components` directory should be exported from `superset-frontend/src/components/index.ts` to facilitate their imports by other components
|
|
|
|
**Component directory name:** Use `PascalCase` for the component directory name
|
|
|
|
**Storybook:** Components should come with a storybook file whenever applicable, with the following naming convention `{ComponentName}.stories.tsx`. More details about Storybook below
|
|
|
|
**Unit and end-to-end tests:** All components should come with unit tests using Jest and React Testing Library. The file name should follow this naming convention `{ComponentName}.test.tsx.` Read the [Testing Guidelines and Best Practices](./testing-guidelines) for more details about tests
|
|
|
|
## Component Development Best Practices
|
|
|
|
### Use TypeScript
|
|
|
|
All new components should be written in TypeScript. This helps catch errors early and provides better development experience with IDE support.
|
|
|
|
```tsx
|
|
interface ComponentProps {
|
|
title: string;
|
|
isVisible?: boolean;
|
|
onClose?: () => void;
|
|
}
|
|
|
|
export const MyComponent: React.FC<ComponentProps> = ({
|
|
title,
|
|
isVisible = true,
|
|
onClose
|
|
}) => {
|
|
// Component implementation
|
|
};
|
|
```
|
|
|
|
### Prefer Functional Components
|
|
|
|
Use functional components with hooks instead of class components:
|
|
|
|
```tsx
|
|
// ✅ Good - Functional component with hooks
|
|
export const MyComponent: React.FC<Props> = ({ data }) => {
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Effect logic
|
|
}, []);
|
|
|
|
return <div>{/* Component JSX */}</div>;
|
|
};
|
|
|
|
// ❌ Avoid - Class component
|
|
class MyComponent extends React.Component {
|
|
// Class implementation
|
|
}
|
|
```
|
|
|
|
### Follow Ant Design Patterns
|
|
|
|
Extend Ant Design components rather than building from scratch:
|
|
|
|
```tsx
|
|
import { Button } from 'antd';
|
|
import styled from '@emotion/styled';
|
|
|
|
const StyledButton = styled(Button)`
|
|
// Custom styling using emotion
|
|
`;
|
|
```
|
|
|
|
### Reusability and Props Design
|
|
|
|
Design components with reusability in mind:
|
|
|
|
```tsx
|
|
interface ButtonProps {
|
|
variant?: 'primary' | 'secondary' | 'tertiary';
|
|
size?: 'small' | 'medium' | 'large';
|
|
loading?: boolean;
|
|
disabled?: boolean;
|
|
children: React.ReactNode;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export const CustomButton: React.FC<ButtonProps> = ({
|
|
variant = 'primary',
|
|
size = 'medium',
|
|
...props
|
|
}) => {
|
|
// Implementation
|
|
};
|
|
```
|
|
|
|
## Testing Components
|
|
|
|
Every component should include comprehensive tests:
|
|
|
|
```tsx
|
|
// MyComponent.test.tsx
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import { MyComponent } from './MyComponent';
|
|
|
|
test('renders component with title', () => {
|
|
render(<MyComponent title="Test Title" />);
|
|
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
|
});
|
|
|
|
test('calls onClose when close button is clicked', () => {
|
|
const mockOnClose = jest.fn();
|
|
render(<MyComponent title="Test" onClose={mockOnClose} />);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: /close/i }));
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
```
|
|
|
|
## Storybook Stories
|
|
|
|
Create stories for visual testing and documentation:
|
|
|
|
```tsx
|
|
// MyComponent.stories.tsx
|
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
import { MyComponent } from './MyComponent';
|
|
|
|
const meta: Meta<typeof MyComponent> = {
|
|
title: 'Components/MyComponent',
|
|
component: MyComponent,
|
|
parameters: {
|
|
layout: 'centered',
|
|
},
|
|
tags: ['autodocs'],
|
|
};
|
|
|
|
export default meta;
|
|
type Story = StoryObj<typeof meta>;
|
|
|
|
export const Default: Story = {
|
|
args: {
|
|
title: 'Default Component',
|
|
isVisible: true,
|
|
},
|
|
};
|
|
|
|
export const Hidden: Story = {
|
|
args: {
|
|
title: 'Hidden Component',
|
|
isVisible: false,
|
|
},
|
|
};
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### Use React.memo for Expensive Components
|
|
|
|
```tsx
|
|
import React, { memo } from 'react';
|
|
|
|
export const ExpensiveComponent = memo<Props>(({ data }) => {
|
|
// Expensive rendering logic
|
|
return <div>{/* Component content */}</div>;
|
|
});
|
|
```
|
|
|
|
### Optimize Re-renders
|
|
|
|
Use `useCallback` and `useMemo` appropriately:
|
|
|
|
```tsx
|
|
export const OptimizedComponent: React.FC<Props> = ({ items, onSelect }) => {
|
|
const expensiveValue = useMemo(() => {
|
|
return items.reduce((acc, item) => acc + item.value, 0);
|
|
}, [items]);
|
|
|
|
const handleSelect = useCallback((id: string) => {
|
|
onSelect(id);
|
|
}, [onSelect]);
|
|
|
|
return <div>{/* Component content */}</div>;
|
|
};
|
|
```
|
|
|
|
## Accessibility
|
|
|
|
Ensure components are accessible:
|
|
|
|
```tsx
|
|
export const AccessibleButton: React.FC<Props> = ({ children, onClick }) => {
|
|
return (
|
|
<button
|
|
type="button"
|
|
aria-label="Descriptive label"
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
};
|
|
```
|
|
|
|
## Error Boundaries
|
|
|
|
For components that might fail, consider error boundaries:
|
|
|
|
```tsx
|
|
export const SafeComponent: React.FC<Props> = ({ children }) => {
|
|
return (
|
|
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
|
{children}
|
|
</ErrorBoundary>
|
|
);
|
|
};
|
|
```
|