mirror of
https://github.com/apache/superset.git
synced 2026-04-17 23:25:05 +00:00
feat(docs): add filterable UI Components table and improve build performance (#38253)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -496,6 +496,7 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
|
||||
return (
|
||||
<div className="database-index">
|
||||
{/* Statistics Cards */}
|
||||
{/* Statistics Cards */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={12} sm={6}>
|
||||
|
||||
262
docs/src/components/ui-components/ComponentIndex.tsx
Normal file
262
docs/src/components/ui-components/ComponentIndex.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Card, Row, Col, Statistic, Table, Tag, Input, Select } from 'antd';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
ApiOutlined,
|
||||
PictureOutlined,
|
||||
PlayCircleOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { ComponentData, ComponentEntry } from './types';
|
||||
|
||||
interface ComponentIndexProps {
|
||||
data: ComponentData;
|
||||
}
|
||||
|
||||
const CATEGORY_COLORS: Record<string, string> = {
|
||||
ui: 'blue',
|
||||
'design-system': 'green',
|
||||
extension: 'purple',
|
||||
};
|
||||
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
ui: 'Core',
|
||||
'design-system': 'Layout',
|
||||
extension: 'Extension',
|
||||
};
|
||||
|
||||
const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
|
||||
|
||||
const { statistics, components } = data;
|
||||
|
||||
const filteredComponents = useMemo(() => {
|
||||
return components
|
||||
.filter((comp) => {
|
||||
const matchesSearch =
|
||||
!searchText ||
|
||||
comp.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
comp.description.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
comp.package.toLowerCase().includes(searchText.toLowerCase());
|
||||
const matchesCategory =
|
||||
!categoryFilter || comp.category === categoryFilter;
|
||||
return matchesSearch && matchesCategory;
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [components, searchText, categoryFilter]);
|
||||
|
||||
const { categories, categoryCounts } = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
components.forEach((comp) => {
|
||||
counts[comp.category] = (counts[comp.category] || 0) + 1;
|
||||
});
|
||||
return {
|
||||
categories: Object.keys(counts).sort(),
|
||||
categoryCounts: counts,
|
||||
};
|
||||
}, [components]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Component',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
sorter: (a: ComponentEntry, b: ComponentEntry) =>
|
||||
a.name.localeCompare(b.name),
|
||||
defaultSortOrder: 'ascend' as const,
|
||||
render: (name: string, record: ComponentEntry) => (
|
||||
<div>
|
||||
<a href={`/${record.docPath}`}>
|
||||
<strong>{name}</strong>
|
||||
</a>
|
||||
{record.description && (
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
{record.description.slice(0, 100)}
|
||||
{record.description.length > 100 ? '...' : ''}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Category',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 120,
|
||||
filters: categories.map((cat) => ({
|
||||
text: CATEGORY_LABELS[cat] || cat,
|
||||
value: cat,
|
||||
})),
|
||||
onFilter: (value: React.Key | boolean, record: ComponentEntry) =>
|
||||
record.category === value,
|
||||
render: (cat: string) => (
|
||||
<Tag color={CATEGORY_COLORS[cat] || 'default'}>
|
||||
{CATEGORY_LABELS[cat] || cat}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Package',
|
||||
dataIndex: 'package',
|
||||
key: 'package',
|
||||
width: 220,
|
||||
render: (pkg: string) => (
|
||||
<code style={{ fontSize: '12px' }}>{pkg}</code>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
key: 'tags',
|
||||
width: 280,
|
||||
filters: [
|
||||
{ text: 'Extension Compatible', value: 'extension' },
|
||||
{ text: 'Gallery', value: 'gallery' },
|
||||
{ text: 'Live Demo', value: 'demo' },
|
||||
],
|
||||
onFilter: (value: React.Key | boolean, record: ComponentEntry) => {
|
||||
switch (value) {
|
||||
case 'extension':
|
||||
return record.extensionCompatible;
|
||||
case 'gallery':
|
||||
return record.hasGallery;
|
||||
case 'demo':
|
||||
return record.hasLiveExample;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
render: (_: unknown, record: ComponentEntry) => (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
{record.extensionCompatible && (
|
||||
<Tag color="purple">Extension Compatible</Tag>
|
||||
)}
|
||||
{record.hasGallery && <Tag color="cyan">Gallery</Tag>}
|
||||
{record.hasLiveExample && <Tag color="green">Demo</Tag>}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Props',
|
||||
dataIndex: 'propsCount',
|
||||
key: 'propsCount',
|
||||
width: 80,
|
||||
sorter: (a: ComponentEntry, b: ComponentEntry) =>
|
||||
a.propsCount - b.propsCount,
|
||||
render: (count: number) => (
|
||||
<span style={{ color: count > 0 ? '#1890ff' : '#999' }}>{count}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="component-index">
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="Total Components"
|
||||
value={statistics.totalComponents}
|
||||
prefix={<AppstoreOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="Extension Compatible"
|
||||
value={statistics.extensionCompatible}
|
||||
prefix={<ApiOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="With Gallery"
|
||||
value={statistics.withGallery}
|
||||
prefix={<PictureOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="With Live Demo"
|
||||
value={statistics.withLiveExample}
|
||||
prefix={<PlayCircleOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Input
|
||||
placeholder="Search components..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Select
|
||||
placeholder="Filter by category"
|
||||
style={{ width: '100%' }}
|
||||
value={categoryFilter}
|
||||
onChange={setCategoryFilter}
|
||||
allowClear
|
||||
options={categories.map((cat) => ({
|
||||
label: (
|
||||
<span>
|
||||
<Tag
|
||||
color={CATEGORY_COLORS[cat] || 'default'}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
{categoryCounts[cat] || 0}
|
||||
</Tag>
|
||||
{CATEGORY_LABELS[cat] || cat}
|
||||
</span>
|
||||
),
|
||||
value: cat,
|
||||
}))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Table
|
||||
dataSource={filteredComponents}
|
||||
columns={columns}
|
||||
rowKey="name"
|
||||
pagination={{
|
||||
defaultPageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `${total} components`,
|
||||
}}
|
||||
size="middle"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentIndex;
|
||||
21
docs/src/components/ui-components/index.ts
Normal file
21
docs/src/components/ui-components/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { default as ComponentIndex } from './ComponentIndex';
|
||||
export * from './types';
|
||||
48
docs/src/components/ui-components/types.ts
Normal file
48
docs/src/components/ui-components/types.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface ComponentEntry {
|
||||
name: string;
|
||||
category: string;
|
||||
categoryLabel: string;
|
||||
description: string;
|
||||
importPath: string;
|
||||
package: string;
|
||||
extensionCompatible: boolean;
|
||||
propsCount: number;
|
||||
controlsCount: number;
|
||||
hasGallery: boolean;
|
||||
hasLiveExample: boolean;
|
||||
docPath: string;
|
||||
storyFile: string;
|
||||
}
|
||||
|
||||
export interface ComponentStatistics {
|
||||
totalComponents: number;
|
||||
byCategory: Record<string, number>;
|
||||
extensionCompatible: number;
|
||||
withGallery: number;
|
||||
withLiveExample: number;
|
||||
}
|
||||
|
||||
export interface ComponentData {
|
||||
generated: string;
|
||||
statistics: ComponentStatistics;
|
||||
components: ComponentEntry[];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-26T01:18:11.347Z",
|
||||
"generated": "2026-02-24T20:28:17.222Z",
|
||||
"statistics": {
|
||||
"totalDatabases": 72,
|
||||
"withDocumentation": 72,
|
||||
@@ -751,14 +751,14 @@
|
||||
"OPEN_SOURCE"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"clickhouse-connect>=0.13.0"
|
||||
"clickhouse-connect>=0.6.8"
|
||||
],
|
||||
"connection_string": "clickhousedb://{username}:{password}@{host}:{port}/{database}",
|
||||
"default_port": 8123,
|
||||
"drivers": [
|
||||
{
|
||||
"name": "clickhouse-connect (Recommended)",
|
||||
"pypi_package": "clickhouse-connect>=0.13.0",
|
||||
"pypi_package": "clickhouse-connect>=0.6.8",
|
||||
"connection_string": "clickhousedb://{username}:{password}@{host}:{port}/{database}",
|
||||
"is_recommended": true,
|
||||
"notes": "Official ClickHouse Python driver with native protocol support."
|
||||
@@ -781,7 +781,7 @@
|
||||
"connection_string": "clickhousedb://localhost/default"
|
||||
}
|
||||
],
|
||||
"install_instructions": "echo \"clickhouse-connect>=0.13.0\" >> ./docker/requirements-local.txt",
|
||||
"install_instructions": "echo \"clickhouse-connect>=0.6.8\" >> ./docker/requirements-local.txt",
|
||||
"compatible_databases": [
|
||||
{
|
||||
"name": "ClickHouse Cloud",
|
||||
@@ -794,7 +794,7 @@
|
||||
"HOSTED_OPEN_SOURCE"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"clickhouse-connect>=0.13.0"
|
||||
"clickhouse-connect>=0.6.8"
|
||||
],
|
||||
"connection_string": "clickhousedb://{username}:{password}@{host}:8443/{database}?secure=true",
|
||||
"parameters": {
|
||||
@@ -816,7 +816,7 @@
|
||||
"HOSTED_OPEN_SOURCE"
|
||||
],
|
||||
"pypi_packages": [
|
||||
"clickhouse-connect>=0.13.0"
|
||||
"clickhouse-connect>=0.6.8"
|
||||
],
|
||||
"connection_string": "clickhousedb://{username}:{password}@{host}/{database}?secure=true",
|
||||
"docs_url": "https://docs.altinity.com/"
|
||||
|
||||
57
docs/src/shims/superset-ui-core.ts
Normal file
57
docs/src/shims/superset-ui-core.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Lightweight shim for @superset-ui/core.
|
||||
*
|
||||
* Components in superset-ui-core/src/components/ self-reference the parent
|
||||
* package for utility imports. This shim re-exports only the specific
|
||||
* utilities they need, avoiding the full barrel file which pulls in heavy
|
||||
* dependencies (d3, query engine, color scales) that cause OOM during the
|
||||
* docs build.
|
||||
*
|
||||
* When adding exports here, verify the source file's imports don't cascade
|
||||
* into heavy modules (connection/SupersetClientClass, query/types, etc.).
|
||||
* Only add leaf-level utilities with minimal transitive dependencies.
|
||||
*
|
||||
* Exports that can't be shimmed safely (e.g. getClientErrorObject, which
|
||||
* pulls in connection/types → SupersetClientClass) will produce
|
||||
* ESModulesLinkingWarnings at build time — these are harmless.
|
||||
*/
|
||||
|
||||
// Paths relative to docs/src/shims/ → superset-frontend/packages/superset-ui-core/src/
|
||||
|
||||
// utils — leaf modules with no heavy transitive deps
|
||||
export { default as ensureIsArray } from '../../../superset-frontend/packages/superset-ui-core/src/utils/ensureIsArray';
|
||||
export {
|
||||
safeHtmlSpan,
|
||||
isProbablyHTML,
|
||||
isJsonString,
|
||||
} from '../../../superset-frontend/packages/superset-ui-core/src/utils/html';
|
||||
|
||||
// hooks
|
||||
export { usePrevious } from '../../../superset-frontend/packages/superset-ui-core/src/hooks/usePrevious/usePrevious';
|
||||
export { useTruncation } from '../../../superset-frontend/packages/superset-ui-core/src/hooks/useTruncation';
|
||||
|
||||
// time-format
|
||||
export { getTimeFormatter } from '../../../superset-frontend/packages/superset-ui-core/src/time-format/TimeFormatterRegistrySingleton';
|
||||
export { default as TimeFormats } from '../../../superset-frontend/packages/superset-ui-core/src/time-format/TimeFormats';
|
||||
|
||||
// color
|
||||
export { hexToRgb } from '../../../superset-frontend/packages/superset-ui-core/src/color/utils';
|
||||
@@ -20,8 +20,8 @@
|
||||
/**
|
||||
* Type declarations for @apache-superset/core/ui
|
||||
*
|
||||
* AUTO-GENERATED by scripts/generate-extension-components.mjs
|
||||
* Do not edit manually - regenerate by running: yarn generate:extension-components
|
||||
* AUTO-GENERATED by scripts/generate-superset-components.mjs
|
||||
* Do not edit manually - regenerate by running: yarn generate:superset-components
|
||||
*/
|
||||
import type { AlertProps as AntdAlertProps } from 'antd/es/alert';
|
||||
import type { PropsWithChildren, FC } from 'react';
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function webpackExtendPlugin(): Plugin<void> {
|
||||
return {
|
||||
devtool: isDev ? false : config.devtool,
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
type: 'filesystem' as const,
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
@@ -129,10 +129,15 @@ export default function webpackExtendPlugin(): Plugin<void> {
|
||||
'react-dom': path.resolve(__dirname, '../node_modules/react-dom'),
|
||||
// Allow importing from superset-frontend
|
||||
src: path.resolve(__dirname, '../../superset-frontend/src'),
|
||||
// '@superset-ui/core': path.resolve(
|
||||
// __dirname,
|
||||
// '../../superset-frontend/packages/superset-ui-core',
|
||||
// ),
|
||||
// Lightweight shim for @superset-ui/core that re-exports only the
|
||||
// utilities needed by components (ensureIsArray, usePrevious, etc.).
|
||||
// Avoids pulling in the full barrel which includes d3, color, query
|
||||
// modules and causes OOM. Required for Rspack which is stricter about
|
||||
// module resolution than webpack.
|
||||
'@superset-ui/core$': path.resolve(
|
||||
__dirname,
|
||||
'./shims/superset-ui-core.ts',
|
||||
),
|
||||
// Add aliases for our components to make imports easier
|
||||
'@docs/components': path.resolve(__dirname, '../src/components'),
|
||||
'@superset/components': path.resolve(
|
||||
|
||||
Reference in New Issue
Block a user