Improve <details> component (#238)

Co-authored-by: Gregory Schier <gschier1990@gmail.com>
This commit is contained in:
Song
2025-07-19 05:28:24 +08:00
committed by GitHub
parent 4a2fb6ed48
commit 0d4b7bb5e2
6 changed files with 95 additions and 65 deletions

View File

@@ -20,6 +20,7 @@ import { showDialog } from '../lib/dialog';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
import { Checkbox } from './core/Checkbox'; import { Checkbox } from './core/Checkbox';
import { DetailsBanner } from './core/DetailsBanner';
import { Editor } from './core/Editor/Editor'; import { Editor } from './core/Editor/Editor';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { Input } from './core/Input'; import { Input } from './core/Input';
@@ -174,22 +175,23 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
); );
case 'accordion': case 'accordion':
return ( return (
<Banner key={i} className={classNames('!p-0', disabled && 'opacity-disabled')}> <DetailsBanner
<details> key={i}
<summary className="px-3 py-1.5 text-text-subtle">{input.label}</summary> summary={input.label}
<div className="mb-3 px-3"> className={classNames(disabled && 'opacity-disabled')}
<FormInputs >
data={data} <div className="mb-3 px-3">
disabled={disabled} <FormInputs
inputs={input.inputs} data={data}
setDataAttr={setDataAttr} disabled={disabled}
stateKey={stateKey} inputs={input.inputs}
autocompleteFunctions={autocompleteFunctions || false} setDataAttr={setDataAttr}
autocompleteVariables={autocompleteVariables} stateKey={stateKey}
/> autocompleteFunctions={autocompleteFunctions || false}
</div> autocompleteVariables={autocompleteVariables}
</details> />
</Banner> </div>
</DetailsBanner>
); );
case 'banner': case 'banner':
return ( return (

View File

@@ -7,9 +7,9 @@ import slugify from 'slugify';
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace'; import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
import { pluralizeCount } from '../lib/pluralize'; import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { Banner } from './core/Banner';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox'; import { Checkbox } from './core/Checkbox';
import { DetailsBanner } from './core/DetailsBanner';
import { HStack, VStack } from './core/Stacks'; import { HStack, VStack } from './core/Stacks';
interface Props { interface Props {
@@ -123,19 +123,14 @@ function ExportDataDialogContent({
))} ))}
</tbody> </tbody>
</table> </table>
<Banner className="!p-0"> <DetailsBanner color="secondary" open summary="Extra Settings">
<details open> <Checkbox
<summary className="px-3 py-2">Extra Settings</summary> checked={includePrivateEnvironments}
<div className="px-3 pb-2"> onChange={setIncludePrivateEnvironments}
<Checkbox title="Include private environments"
checked={includePrivateEnvironments} help='Environments marked as "sharable" will be exported by default'
onChange={setIncludePrivateEnvironments} />
title="Include private environments" </DetailsBanner>
help='Environments marked as "sharable" will be exported by default'
/>
</div>
</details>
</Banner>
<HStack space={2} justifyContent="end"> <HStack space={2} justifyContent="end">
<Button className="focus" variant="border" onClick={onHide}> <Button className="focus" variant="border" onClick={onHide}>
Cancel Cancel

View File

@@ -5,8 +5,8 @@ import { connections } from '../lib/data/connections';
import { encodings } from '../lib/data/encodings'; import { encodings } from '../lib/data/encodings';
import { headerNames } from '../lib/data/headerNames'; import { headerNames } from '../lib/data/headerNames';
import { mimeTypes } from '../lib/data/mimetypes'; import { mimeTypes } from '../lib/data/mimetypes';
import { Banner } from './core/Banner';
import { CountBadge } from './core/CountBadge'; import { CountBadge } from './core/CountBadge';
import { DetailsBanner } from './core/DetailsBanner';
import type { GenericCompletionConfig } from './core/Editor/genericCompletion'; import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
import type { InputProps } from './core/Input'; import type { InputProps } from './core/Input';
import type { Pair, PairEditorProps } from './core/PairEditor'; import type { Pair, PairEditorProps } from './core/PairEditor';
@@ -35,35 +35,36 @@ export function HeadersEditor({
return ( return (
<div className="@container w-full h-full grid grid-rows-[auto_minmax(0,1fr)]"> <div className="@container w-full h-full grid grid-rows-[auto_minmax(0,1fr)]">
{validInheritedHeaders.length > 0 ? ( {validInheritedHeaders.length > 0 ? (
<Banner className="!py-0 mb-1.5 border-dashed" color="secondary"> <DetailsBanner
<details> color="secondary"
<summary className="py-1.5 text-sm !cursor-default !select-none opacity-70 hover:opacity-100"> className="text-sm mb-1.5"
<HStack> summary={
Inherited <CountBadge count={validInheritedHeaders.length} /> <HStack>
</HStack> Inherited <CountBadge count={validInheritedHeaders.length} />
</summary> </HStack>
<div className="pb-2"> }
{validInheritedHeaders?.map((pair, i) => ( >
<PairEditorRow <div className="pb-2">
key={pair.id + '.' + i} {validInheritedHeaders?.map((pair, i) => (
index={i} <PairEditorRow
disabled key={pair.id + '.' + i}
disableDrag index={i}
className="py-1" disabled
onChange={() => {}} disableDrag
onEnd={() => {}} className="py-1"
onMove={() => {}} onChange={() => {}}
pair={ensurePairId(pair)} onEnd={() => {}}
stateKey={null} onMove={() => {}}
nameAutocompleteFunctions pair={ensurePairId(pair)}
nameAutocompleteVariables stateKey={null}
valueAutocompleteFunctions nameAutocompleteFunctions
valueAutocompleteVariables nameAutocompleteVariables
/> valueAutocompleteFunctions
))} valueAutocompleteVariables
</div> />
</details> ))}
</Banner> </div>
</DetailsBanner>
) : ( ) : (
<span /> <span />
)} )}

View File

@@ -1,4 +1,5 @@
import { Button } from './core/Button'; import { Button } from './core/Button';
import { DetailsBanner } from './core/DetailsBanner';
import { FormattedError } from './core/FormattedError'; import { FormattedError } from './core/FormattedError';
import { Heading } from './core/Heading'; import { Heading } from './core/Heading';
import { VStack } from './core/Stacks'; import { VStack } from './core/Stacks';
@@ -17,10 +18,9 @@ export default function RouteError({ error }: { error: unknown }) {
<FormattedError> <FormattedError>
{message} {message}
{stack && ( {stack && (
<details className="mt-3 select-auto text-xs"> <DetailsBanner color="secondary" className="mt-3 select-auto text-xs" summary="Stack Trace">
<summary className="!cursor-default !select-none">Stack Trace</summary>
<div className="mt-2 text-xs">{stack}</div> <div className="mt-2 text-xs">{stack}</div>
</details> </DetailsBanner>
)} )}
</FormattedError> </FormattedError>
<VStack space={2}> <VStack space={2}>

View File

@@ -13,9 +13,10 @@ export function Banner({ children, className, color }: BannerProps) {
<div <div
className={classNames( className={classNames(
className, className,
color && 'bg-surface',
`x-theme-banner--${color}`, `x-theme-banner--${color}`,
'border border-border bg-surface', 'border border-border border-dashed',
'px-4 py-3 rounded-lg select-auto', 'px-4 py-2 rounded-lg select-auto',
'overflow-auto text-text', 'overflow-auto text-text',
)} )}
> >

View File

@@ -0,0 +1,31 @@
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import type { BannerProps } from './Banner';
import { Banner } from './Banner';
interface Props extends HTMLAttributes<HTMLDetailsElement> {
summary: ReactNode;
color?: BannerProps['color'];
open?: boolean;
}
export function DetailsBanner({ className, color, summary, children, ...extraProps }: Props) {
return (
<Banner color={color} className={className}>
<details className="group list-none" {...extraProps}>
<summary className="!cursor-default !select-none list-none flex items-center gap-2 focus:outline-none opacity-70 hover:opacity-100 focus:opacity-100">
<div
className={classNames(
'transition-transform',
'group-open:rotate-90',
'w-0 h-0 border-t-[0.3em] border-b-[0.3em] border-l-[0.5em] border-r-0',
'border-t-transparent border-b-transparent border-l-text-subtle',
)}
></div>
{summary}
</summary>
{children}
</details>
</Banner>
);
}