Scrollable tables, specify multi-part filename, fix required prop in prompt, better tab padding

This commit is contained in:
Gregory Schier
2025-11-25 08:45:33 -08:00
parent 0cad8f69e2
commit c4ab2965f7
16 changed files with 245 additions and 144 deletions

View File

@@ -10,6 +10,7 @@
"core:event:allow-listen", "core:event:allow-listen",
"core:event:allow-unlisten", "core:event:allow-unlisten",
"core:path:allow-resolve-directory", "core:path:allow-resolve-directory",
"core:path:allow-basename",
"os:allow-os-type", "os:allow-os-type",
"clipboard-manager:allow-clear", "clipboard-manager:allow-clear",
"clipboard-manager:allow-write-text", "clipboard-manager:allow-write-text",

View File

@@ -398,11 +398,16 @@ pub async fn send_http_request_with_context<R: Runtime>(
// Set a file path if it is not empty // Set a file path if it is not empty
if !file_path.is_empty() { if !file_path.is_empty() {
let filename = PathBuf::from(file_path) let user_filename = get_str(p, "filename").to_owned();
.file_name() let filename = if user_filename.is_empty() {
.unwrap_or_default() PathBuf::from(file_path)
.to_string_lossy() .file_name()
.to_string(); .unwrap_or_default()
.to_string_lossy()
.to_string()
} else {
user_filename
};
part = part.file_name(filename); part = part.file_name(filename);
} }

View File

@@ -1,9 +1,16 @@
import type { Folder } from '@yaakapp-internal/models'; import { createWorkspaceModel, type Folder, modelTypeLabel } from '@yaakapp-internal/models';
import { createWorkspaceModel } from '@yaakapp-internal/models';
import { applySync, calculateSync } from '@yaakapp-internal/sync'; import { applySync, calculateSync } from '@yaakapp-internal/sync';
import { Banner } from '../components/core/Banner'; import { Banner } from '../components/core/Banner';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { VStack } from '../components/core/Stacks'; import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
TruncatedWideTableCell,
} from '../components/core/Table';
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace'; import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { showConfirm } from '../lib/confirm'; import { showConfirm } from '../lib/confirm';
@@ -76,63 +83,72 @@ export const syncWorkspace = createFastMutation<
: await showConfirm({ : await showConfirm({
id: 'commit-sync', id: 'commit-sync',
title: 'Changes Detected', title: 'Changes Detected',
size: 'md',
confirmText: 'Apply Changes', confirmText: 'Apply Changes',
color: isDeletingWorkspace ? 'danger' : 'primary', color: isDeletingWorkspace ? 'danger' : 'primary',
description: ( description: (
<VStack space={3}> <div className="h-full grid grid-rows-[auto_auto_minmax(0,1fr)] gap-3">
{isDeletingWorkspace && ( {isDeletingWorkspace ? (
<Banner color="danger"> <Banner color="danger">
🚨 <strong>Changes contain a workspace deletion!</strong> 🚨 <strong>Changes contain a workspace deletion!</strong>
</Banner> </Banner>
) : (
<span />
)} )}
<p> <p>
{pluralizeCount('file', dbOps.length)} in the directory{' '} {pluralizeCount('file', dbOps.length)} in the directory{' '}
{dbOps.length === 1 ? 'has' : 'have'} changed. Do you want to update your workspace? {dbOps.length === 1 ? 'has' : 'have'} changed. Do you want to update your workspace?
</p> </p>
<div className="overflow-y-auto max-h-[10rem]"> <Table scrollable>
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight"> <TableHead>
<thead> <TableRow>
<tr> <TableHeaderCell>Type</TableHeaderCell>
<th className="py-1 text-left">Name</th> <TableHeaderCell>Name</TableHeaderCell>
<th className="py-1 text-right pl-4">Operation</th> <TableHeaderCell>Operation</TableHeaderCell>
</tr> </TableRow>
</thead> </TableHead>
<tbody className="divide-y divide-surface-highlight"> <TableBody>
{dbOps.map((op, i) => { {dbOps.map((op, i) => {
let name = ''; let name: string;
let label = ''; let label: string;
let color = ''; let color: string;
let model: string;
if (op.type === 'dbCreate') { if (op.type === 'dbCreate') {
label = 'create'; label = 'create';
name = resolvedModelNameWithFolders(op.fs.model); name = resolvedModelNameWithFolders(op.fs.model);
color = 'text-success'; color = 'text-success';
} else if (op.type === 'dbUpdate') { model = modelTypeLabel(op.fs.model);
label = 'update'; } else if (op.type === 'dbUpdate') {
name = resolvedModelNameWithFolders(op.fs.model); label = 'update';
color = 'text-info'; name = resolvedModelNameWithFolders(op.fs.model);
} else if (op.type === 'dbDelete') { color = 'text-info';
label = 'delete'; model = modelTypeLabel(op.fs.model);
name = resolvedModelNameWithFolders(op.model); } else if (op.type === 'dbDelete') {
color = 'text-danger'; label = 'delete';
} else { name = resolvedModelNameWithFolders(op.model);
return null; color = 'text-danger';
} model = modelTypeLabel(op.model);
} else {
return null;
}
return ( return (
// biome-ignore lint/suspicious/noArrayIndexKey: none // biome-ignore lint/suspicious/noArrayIndexKey: none
<tr key={i} className="text-text"> <TableRow key={i}>
<td className="py-1">{name}</td> <TableCell>{model}</TableCell>
<td className="py-1 pl-4 text-right"> <TruncatedWideTableCell className="text-text">
<InlineCode className={color}>{label}</InlineCode> {name}
</td> </TruncatedWideTableCell>
</tr> <TableCell className="text-right">
); <InlineCode className={color}>{label}</InlineCode>
})} </TableCell>
</tbody> </TableRow>
</table> );
</div> })}
</VStack> </TableBody>
</Table>
</div>
), ),
}); });
if (confirmed) { if (confirmed) {

View File

@@ -17,6 +17,7 @@ export function FormMultipartEditor({ request, forceUpdateKey, onChange }: Props
name: p.name, name: p.name,
value: p.file ?? p.value, value: p.file ?? p.value,
contentType: p.contentType, contentType: p.contentType,
filename: p.filename,
isFile: !!p.file, isFile: !!p.file,
id: p.id, id: p.id,
})), })),
@@ -30,6 +31,7 @@ export function FormMultipartEditor({ request, forceUpdateKey, onChange }: Props
enabled: p.enabled, enabled: p.enabled,
name: p.name, name: p.name,
contentType: p.contentType, contentType: p.contentType,
filename: p.filename,
file: p.isFile ? p.value : undefined, file: p.isFile ? p.value : undefined,
value: p.isFile ? undefined : p.value, value: p.isFile ? undefined : p.value,
id: p.id, id: p.id,

View File

@@ -14,6 +14,7 @@ import { HStack } from './core/Stacks';
type Props = Omit<ButtonProps, 'type'> & { type Props = Omit<ButtonProps, 'type'> & {
onChange: (value: { filePath: string | null; contentType: string | null }) => void; onChange: (value: { filePath: string | null; contentType: string | null }) => void;
filePath: string | null; filePath: string | null;
nameOverride?: string | null;
directory?: boolean; directory?: boolean;
inline?: boolean; inline?: boolean;
noun?: string; noun?: string;
@@ -31,6 +32,7 @@ export function SelectFile({
className, className,
directory, directory,
noun, noun,
nameOverride,
size = 'sm', size = 'sm',
label, label,
help, help,
@@ -88,6 +90,8 @@ export function SelectFile({
}; };
}, [isHovering, onChange]); }, [isHovering, onChange]);
const filePathWithNameOverride = nameOverride ? `${filePath} (${nameOverride})` : filePath;
return ( return (
<div ref={ref} className="w-full"> <div ref={ref} className="w-full">
{label && ( {label && (
@@ -110,7 +114,7 @@ export function SelectFile({
{...props} {...props}
> >
{rtlEscapeChar} {rtlEscapeChar}
{inline ? filePath || selectOrChange : selectOrChange} {inline ? filePathWithNameOverride || selectOrChange : selectOrChange}
</Button> </Button>
{!inline && ( {!inline && (

View File

@@ -82,22 +82,22 @@ export default function Settings({ hide }: Props) {
}), }),
)} )}
> >
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-8 !py-4"> <TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-6 !py-4">
<SettingsGeneral /> <SettingsGeneral />
</TabContent> </TabContent>
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-8 !py-4"> <TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-6 !py-4">
<SettingsInterface /> <SettingsInterface />
</TabContent> </TabContent>
<TabContent value={TAB_THEME} className="overflow-y-auto h-full px-8 !py-4"> <TabContent value={TAB_THEME} className="overflow-y-auto h-full px-6 !py-4">
<SettingsTheme /> <SettingsTheme />
</TabContent> </TabContent>
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 px-8 !py-4"> <TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 px-6 !py-4">
<SettingsPlugins /> <SettingsPlugins />
</TabContent> </TabContent>
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-8 !py-4"> <TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-6 !py-4">
<SettingsProxy /> <SettingsProxy />
</TabContent> </TabContent>
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-8 !py-4"> <TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-6 !py-4">
<SettingsLicense /> <SettingsLicense />
</TabContent> </TabContent>
</Tabs> </Tabs>

View File

@@ -242,7 +242,7 @@ function PluginSearch() {
defaultValue={query} defaultValue={query}
/> />
</HStack> </HStack>
<div className="w-full h-full overflow-y-auto"> <div className="w-full h-full">
{results.data == null ? ( {results.data == null ? (
<EmptyStateText> <EmptyStateText>
<LoadingIcon size="xl" className="text-text-subtlest" /> <LoadingIcon size="xl" className="text-text-subtlest" />
@@ -250,7 +250,7 @@ function PluginSearch() {
) : (results.data.plugins ?? []).length === 0 ? ( ) : (results.data.plugins ?? []).length === 0 ? (
<EmptyStateText>No plugins found</EmptyStateText> <EmptyStateText>No plugins found</EmptyStateText>
) : ( ) : (
<Table> <Table scrollable>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableHeaderCell>Name</TableHeaderCell> <TableHeaderCell>Name</TableHeaderCell>
@@ -282,7 +282,7 @@ function InstalledPlugins() {
</EmptyStateText> </EmptyStateText>
</div> </div>
) : ( ) : (
<Table> <Table scrollable>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableHeaderCell>Name</TableHeaderCell> <TableHeaderCell>Name</TableHeaderCell>

View File

@@ -69,7 +69,11 @@ export function Dialog({
animate={{ top: 0, scale: 1 }} animate={{ top: 0, scale: 1 }}
className={classNames( className={classNames(
className, className,
'grid grid-rows-[auto_auto_minmax(0,1fr)]', 'grid',
title != null && description != null && 'grid-rows-[auto_minmax(0,1fr)_minmax_(0,1fr)]',
title == null && description != null && 'grid-rows-[auto_minmax(0,1fr)]',
title != null && description == null && 'grid-rows-[auto_minmax(0,1fr)]',
title == null && description == null && 'grid-rows-[minmax(0,1fr)]',
'grid-cols-1', // must be here for inline code blocks to correctly break words 'grid-cols-1', // must be here for inline code blocks to correctly break words
'relative bg-surface pointer-events-auto', 'relative bg-surface pointer-events-auto',
'rounded-lg', 'rounded-lg',
@@ -83,20 +87,16 @@ export function Dialog({
size === 'dynamic' && 'min-w-[20rem] max-w-[100vw]', size === 'dynamic' && 'min-w-[20rem] max-w-[100vw]',
)} )}
> >
{title ? ( {title && (
<Heading className="px-6 mt-4 mb-2" level={1} id={titleId}> <Heading className="px-6 mt-4 mb-2" level={1} id={titleId}>
{title} {title}
</Heading> </Heading>
) : (
<span aria-hidden />
)} )}
{description ? ( {description && (
<div className="px-6 text-text-subtle mb-3" id={descriptionId}> <div className="min-h-0 px-6 text-text-subtle mb-3" id={descriptionId}>
{description} {description}
</div> </div>
) : (
<span aria-hidden />
)} )}
<div <div

View File

@@ -9,6 +9,7 @@ import {
useSensor, useSensor,
useSensors, useSensors,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { basename } from '@tauri-apps/api/path';
import classNames from 'classnames'; import classNames from 'classnames';
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { WrappedEnvironmentVariable } from '../../hooks/useEnvironmentVariables'; import type { WrappedEnvironmentVariable } from '../../hooks/useEnvironmentVariables';
@@ -70,6 +71,7 @@ export type Pair = {
name: string; name: string;
value: string; value: string;
contentType?: string; contentType?: string;
filename?: string;
isFile?: boolean; isFile?: boolean;
readOnlyName?: boolean; readOnlyName?: boolean;
}; };
@@ -492,6 +494,11 @@ export function PairEditorRow({
[onChange, pair], [onChange, pair],
); );
const handleChangeValueFilename = useMemo(
() => (filename: string) => onChange?.({ ...pair, filename }),
[onChange, pair],
);
const handleEditMultiLineValue = useCallback( const handleEditMultiLineValue = useCallback(
() => () =>
showDialog({ showDialog({
@@ -614,6 +621,7 @@ export function PairEditorRow({
inline inline
size="xs" size="xs"
filePath={pair.value} filePath={pair.value}
nameOverride={pair.filename || null}
onChange={handleChangeValueFile} onChange={handleChangeValueFile}
/> />
) : pair.value.includes('\n') ? ( ) : pair.value.includes('\n') ? (
@@ -659,6 +667,7 @@ export function PairEditorRow({
onChangeFile={handleChangeValueFile} onChangeFile={handleChangeValueFile}
onChangeText={handleChangeValueText} onChangeText={handleChangeValueText}
onChangeContentType={handleChangeValueContentType} onChangeContentType={handleChangeValueContentType}
onChangeFilename={handleChangeValueFilename}
onDelete={handleDelete} onDelete={handleDelete}
editMultiLine={handleEditMultiLineValue} editMultiLine={handleEditMultiLineValue}
/> />
@@ -687,6 +696,7 @@ function FileActionsDropdown({
onChangeFile, onChangeFile,
onChangeText, onChangeText,
onChangeContentType, onChangeContentType,
onChangeFilename,
onDelete, onDelete,
editMultiLine, editMultiLine,
}: { }: {
@@ -694,6 +704,7 @@ function FileActionsDropdown({
onChangeFile: ({ filePath }: { filePath: string | null }) => void; onChangeFile: ({ filePath }: { filePath: string | null }) => void;
onChangeText: (text: string) => void; onChangeText: (text: string) => void;
onChangeContentType: (contentType: string) => void; onChangeContentType: (contentType: string) => void;
onChangeFilename: (filename: string) => void;
onDelete: () => void; onDelete: () => void;
editMultiLine: () => void; editMultiLine: () => void;
}) { }) {
@@ -731,6 +742,26 @@ function FileActionsDropdown({
onChangeContentType(contentType); onChangeContentType(contentType);
}, },
}, },
{
label: 'Set File Name',
leftSlot: <Icon icon="file_code" />,
onSelect: async () => {
console.log('PAIR', pair);
const defaultFilename = await basename(pair.value ?? '');
const filename = await showPrompt({
id: 'filename',
title: 'Override Filename',
label: 'Filename',
required: false,
placeholder: defaultFilename ?? 'myfile.png',
defaultValue: pair.filename,
confirmText: 'Set',
description: 'Leave blank to use the name of the selected file',
});
if (filename == null) return;
onChangeFilename(filename);
},
},
{ {
label: 'Unset File', label: 'Unset File',
leftSlot: <Icon icon="x" />, leftSlot: <Icon icon="x" />,
@@ -747,7 +778,17 @@ function FileActionsDropdown({
color: 'danger', color: 'danger',
}, },
], ],
[editMultiLine, onChangeContentType, onChangeFile, onDelete, pair.contentType, pair.isFile], [
editMultiLine,
onChangeContentType,
onChangeFile,
onDelete,
pair.contentType,
pair.isFile,
onChangeFilename,
pair.filename,
pair,
],
); );
return ( return (

View File

@@ -1,20 +1,50 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
export function Table({ children }: { children: ReactNode }) { export function Table({
children,
className,
scrollable,
}: {
children: ReactNode;
className?: string;
scrollable?: boolean;
}) {
return ( return (
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight"> <div className={classNames('w-full', scrollable && 'h-full overflow-y-auto')}>
{children} <table
</table> className={classNames(
className,
'w-full text-sm mb-auto min-w-full max-w-full',
'border-separate border-spacing-0',
scrollable && '[&_thead]:sticky [&_thead]:top-0 [&_thead]:z-10',
)}
>
{children}
</table>
</div>
); );
} }
export function TableBody({ children }: { children: ReactNode }) { export function TableBody({ children }: { children: ReactNode }) {
return <tbody className="divide-y divide-surface-highlight">{children}</tbody>; return (
<tbody className="[&>tr:not(:last-child)>td]:border-b [&>tr:not(:last-child)>td]:border-b-surface-highlight">
{children}
</tbody>
);
} }
export function TableHead({ children }: { children: ReactNode }) { export function TableHead({ children, className }: { children: ReactNode; className?: string }) {
return <thead>{children}</thead>; return (
<thead
className={classNames(
className,
'bg-surface [&_th]:border-b [&_th]:border-b-surface-highlight',
)}
>
{children}
</thead>
);
} }
export function TableRow({ children }: { children: ReactNode }) { export function TableRow({ children }: { children: ReactNode }) {
@@ -42,9 +72,7 @@ export function TruncatedWideTableCell({
className?: string; className?: string;
}) { }) {
return ( return (
<TableCell className={classNames(className, 'w-full relative')}> <TableCell className={classNames(className, 'truncate max-w-0 w-full')}>{children}</TableCell>
<div className="absolute inset-0 py-2 truncate">{children}</div>
</TableCell>
); );
} }

View File

@@ -87,7 +87,7 @@ export function Tabs({
addBorders && layout === 'horizontal' && 'pl-3 -ml-1', addBorders && layout === 'horizontal' && 'pl-3 -ml-1',
addBorders && layout === 'vertical' && 'ml-0 mb-2', addBorders && layout === 'vertical' && 'ml-0 mb-2',
'flex items-center hide-scrollbars', 'flex items-center hide-scrollbars',
layout === 'horizontal' && 'h-full overflow-auto p-2 -mr-2', layout === 'horizontal' && 'h-full overflow-auto p-2',
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ', layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
// Give space for button focus states within overflow boundary. // Give space for button focus states within overflow boundary.
!addBorders && layout === 'vertical' && 'py-1 pl-3 -ml-5 pr-1', !addBorders && layout === 'vertical' && 'py-1 pl-3 -ml-5 pr-1',

View File

@@ -107,6 +107,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
id: 'git-history', id: 'git-history',
size: 'md', size: 'md',
title: 'Commit History', title: 'Commit History',
noPadding: true,
render: () => <HistoryDialog log={log.data ?? []} />, render: () => <HistoryDialog log={log.data ?? []} />,
}); });
}, },

View File

@@ -15,45 +15,43 @@ export function GitRemotesDialog({ dir }: Props) {
const [{ remotes }, { rmRemote }] = useGit(dir, gitCallbacks(dir)); const [{ remotes }, { rmRemote }] = useGit(dir, gitCallbacks(dir));
return ( return (
<div> <Table scrollable>
<Table> <TableHead>
<TableHead> <TableRow>
<TableRow> <TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Name</TableHeaderCell> <TableHeaderCell>URL</TableHeaderCell>
<TableHeaderCell>URL</TableHeaderCell> <TableHeaderCell>
<TableHeaderCell> <Button
<Button className="text-text-subtle ml-auto"
size="2xs"
color="primary"
title="Add remote"
variant="border"
onClick={() => addGitRemote(dir)}
>
Add Remote
</Button>
</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{remotes.data?.map((r) => (
<TableRow key={r.name + r.url}>
<TableCell>{r.name}</TableCell>
<TableCell>{r.url}</TableCell>
<TableCell>
<IconButton
size="sm"
className="text-text-subtle ml-auto" className="text-text-subtle ml-auto"
size="2xs" icon="trash"
color="primary" title="Remove remote"
title="Add remote" onClick={() => rmRemote.mutate({ name: r.name })}
variant="border" />
onClick={() => addGitRemote(dir)} </TableCell>
>
Add Remote
</Button>
</TableHeaderCell>
</TableRow> </TableRow>
</TableHead> ))}
<TableBody> </TableBody>
{remotes.data?.map((r) => ( </Table>
<TableRow key={r.name + r.url}>
<TableCell>{r.name}</TableCell>
<TableCell>{r.url}</TableCell>
<TableCell>
<IconButton
size="sm"
className="text-text-subtle ml-auto"
icon="trash"
title="Remove remote"
onClick={() => rmRemote.mutate({ name: r.name })}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
); );
} }

View File

@@ -16,29 +16,31 @@ interface Props {
export function HistoryDialog({ log }: Props) { export function HistoryDialog({ log }: Props) {
return ( return (
<Table> <div className="pl-5 pr-1 pb-1">
<TableHead> <Table scrollable className="px-1">
<TableRow> <TableHead>
<TableHeaderCell>Message</TableHeaderCell> <TableRow>
<TableHeaderCell>Author</TableHeaderCell> <TableHeaderCell>Message</TableHeaderCell>
<TableHeaderCell>When</TableHeaderCell> <TableHeaderCell>Author</TableHeaderCell>
</TableRow> <TableHeaderCell>When</TableHeaderCell>
</TableHead>
<TableBody>
{log.map((l) => (
<TableRow key={l.author + (l.message ?? 'n/a') + l.when}>
<TruncatedWideTableCell>
{l.message || <em className="text-text-subtle">No message</em>}
</TruncatedWideTableCell>
<TableCell>
<span title={`Email: ${l.author.email}`}>{l.author.name || 'Unknown'}</span>
</TableCell>
<TableCell className="text-text-subtle">
<span title={l.when}>{formatDistanceToNowStrict(l.when)} ago</span>
</TableCell>
</TableRow> </TableRow>
))} </TableHead>
</TableBody> <TableBody>
</Table> {log.map((l) => (
<TableRow key={l.author + (l.message ?? 'n/a') + l.when}>
<TruncatedWideTableCell>
{l.message || <em className="text-text-subtle">No message</em>}
</TruncatedWideTableCell>
<TableCell>
<span title={`Email: ${l.author.email}`}>{l.author.name || 'Unknown'}</span>
</TableCell>
<TableCell className="text-text-subtle">
<span title={l.when}>{formatDistanceToNowStrict(l.when)} ago</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
); );
} }

View File

@@ -5,20 +5,21 @@ import { showDialog } from './dialog';
type ConfirmArgs = { type ConfirmArgs = {
id: string; id: string;
} & Pick<DialogProps, 'title' | 'description'> & } & Pick<DialogProps, 'title' | 'description' | 'size'> &
Pick<ConfirmProps, 'color' | 'confirmText' | 'requireTyping'>; Pick<ConfirmProps, 'color' | 'confirmText' | 'requireTyping'>;
export async function showConfirm({ export async function showConfirm({
color, color,
confirmText, confirmText,
requireTyping, requireTyping,
size = 'sm',
...extraProps ...extraProps
}: ConfirmArgs) { }: ConfirmArgs) {
return new Promise((onResult: ConfirmProps['onResult']) => { return new Promise((onResult: ConfirmProps['onResult']) => {
showDialog({ showDialog({
...extraProps, ...extraProps,
hideX: true, hideX: true,
size: 'sm', size,
disableBackdropClose: true, // Prevent accidental dismisses disableBackdropClose: true, // Prevent accidental dismisses
render: ({ hide }) => Confirm({ onHide: hide, color, onResult, confirmText, requireTyping }), render: ({ hide }) => Confirm({ onHide: hide, color, onResult, confirmText, requireTyping }),
}); });

View File

@@ -18,11 +18,13 @@ export async function showPrompt({
description, description,
cancelText, cancelText,
confirmText, confirmText,
required,
...props ...props
}: PromptArgs) { }: PromptArgs) {
const inputs: FormInput[] = [ const inputs: FormInput[] = [
{ {
...props, ...props,
optional: !required,
type: 'text', type: 'text',
name: 'value', name: 'value',
}, },