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:
Evan Rusackas
2026-03-04 08:32:25 -05:00
committed by GitHub
parent 983b633972
commit ef4b1d674b
21 changed files with 1597 additions and 833 deletions

View File

@@ -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}>

View 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;

View 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';

View 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[];
}

View File

@@ -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/"

View 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';

View File

@@ -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';

View File

@@ -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(