feat: enhance PluginsPage layout with flexible tab content and improved overflow handling

This commit is contained in:
shiyu
2025-12-15 15:38:28 +08:00
parent 18f59f8d33
commit 388ddfd869
2 changed files with 78 additions and 48 deletions
+25
View File
@@ -66,3 +66,28 @@ body { font-family: system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto
.processors-tabs .ant-tabs-tabpane-active { .processors-tabs .ant-tabs-tabpane-active {
display: flex; display: flex;
} }
.plugins-tabs {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.plugins-tabs .ant-tabs-content-holder,
.plugins-tabs .ant-tabs-content {
flex: 1;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
}
.plugins-tabs .ant-tabs-tabpane {
flex: 1;
height: 100%;
min-height: 0;
display: none;
flex-direction: column;
}
.plugins-tabs .ant-tabs-tabpane-active {
display: flex;
}
+53 -48
View File
@@ -340,7 +340,7 @@ const PluginsPage = memo(function PluginsPage() {
}; };
return ( return (
<> <div style={{ height: 'calc(100vh - 88px)', display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
<Button type="primary" onClick={() => setAdding(true)}>{t('Install App')}</Button> <Button type="primary" onClick={() => setAdding(true)}>{t('Install App')}</Button>
{tab === 'installed' && <Button onClick={reload} loading={loading}>{t('Refresh')}</Button>} {tab === 'installed' && <Button onClick={reload} loading={loading}>{t('Refresh')}</Button>}
@@ -350,12 +350,13 @@ const PluginsPage = memo(function PluginsPage() {
<Tabs <Tabs
activeKey={tab} activeKey={tab}
onChange={(k) => setTab(k as any)} onChange={(k) => setTab(k as any)}
className="plugins-tabs"
items={[ items={[
{ {
key: 'installed', key: 'installed',
label: t('Installed'), label: t('Installed'),
children: ( children: (
<> <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<Input <Input
placeholder={t('Search name/author/url/extension')} placeholder={t('Search name/author/url/extension')}
@@ -366,30 +367,32 @@ const PluginsPage = memo(function PluginsPage() {
onPressEnter={() => reload()} onPressEnter={() => reload()}
/> />
</div> </div>
{loading ? ( <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: 4 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}> {loading ? (
{Array.from({ length: 6 }).map((_, i) => ( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}>
<Card key={i} style={{ borderRadius: 10 }}> {Array.from({ length: 6 }).map((_, i) => (
<Skeleton active avatar paragraph={{ rows: 3 }} /> <Card key={i} style={{ borderRadius: 10 }}>
</Card> <Skeleton active avatar paragraph={{ rows: 3 }} />
))} </Card>
</div> ))}
) : (filteredSystemApps.length + filtered.length) === 0 ? ( </div>
<Empty description={t('No plugins')} /> ) : (filteredSystemApps.length + filtered.length) === 0 ? (
) : ( <Empty description={t('No plugins')} />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 12 }}> ) : (
{filteredSystemApps.map(renderSystemCard)} <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 12 }}>
{filtered.map(renderCard)} {filteredSystemApps.map(renderSystemCard)}
</div> {filtered.map(renderCard)}
)} </div>
</> )}
</div>
</div>
) )
}, },
{ {
key: 'discover', key: 'discover',
label: t('Discover'), label: t('Discover'),
children: ( children: (
<> <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
<Input <Input
placeholder={t('Search apps')} placeholder={t('Search apps')}
@@ -417,34 +420,36 @@ const PluginsPage = memo(function PluginsPage() {
Foxel Center Foxel Center
</Button> </Button>
</div> </div>
{repoLoading ? ( <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: 4 }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}> {repoLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 12 }}>
<Card key={i} style={{ borderRadius: 10 }}> {Array.from({ length: 6 }).map((_, i) => (
<Skeleton active avatar paragraph={{ rows: 3 }} /> <Card key={i} style={{ borderRadius: 10 }}>
</Card> <Skeleton active avatar paragraph={{ rows: 3 }} />
))} </Card>
</div> ))}
) : repoItems.length === 0 ? (
<Empty description={t('No results')} />
) : (
<>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 12 }}>
{repoItems.map(renderRepoCard)}
</div> </div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 12 }}> ) : repoItems.length === 0 ? (
<Pagination <Empty description={t('No results')} />
current={repoPage} ) : (
pageSize={repoPageSize} <>
total={repoTotal} <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 12 }}>
showSizeChanger {repoItems.map(renderRepoCard)}
pageSizeOptions={[12, 24, 48].map(String)} </div>
onChange={(p, ps) => { setRepoPage(p); setRepoPageSize(ps); }} <div style={{ display: 'flex', justifyContent: 'center', marginTop: 12 }}>
/> <Pagination
</div> current={repoPage}
</> pageSize={repoPageSize}
)} total={repoTotal}
</> showSizeChanger
pageSizeOptions={[12, 24, 48].map(String)}
onChange={(p, ps) => { setRepoPage(p); setRepoPageSize(ps); }}
/>
</div>
</>
)}
</div>
</div>
) )
} }
]} ]}
@@ -463,7 +468,7 @@ const PluginsPage = memo(function PluginsPage() {
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
</> </div>
); );
}); });