mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:14:03 +01:00
Tweak workspace settings and a bunch of small things
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import type { InternalEvent } from '@yaakapp-internal/plugins';
|
||||
import type { ShowToastRequest } from '@yaakapp/api';
|
||||
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
import { useSubscribeHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||
import { generateId } from '../lib/generateId';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { showToast } from '../lib/toast';
|
||||
|
||||
export function GlobalHooks() {
|
||||
useSyncZoomSetting();
|
||||
@@ -25,32 +17,7 @@ export function GlobalHooks() {
|
||||
useSubscribeHttpAuthentication();
|
||||
|
||||
// Other useful things
|
||||
useNotificationToast();
|
||||
useActiveWorkspaceChangedToast();
|
||||
|
||||
// Listen for toasts
|
||||
useListenToTauriEvent<ShowToastRequest>('show_toast', (event) => {
|
||||
showToast({ ...event.payload });
|
||||
});
|
||||
|
||||
// Listen for plugin events
|
||||
useListenToTauriEvent<InternalEvent>('plugin_event', async ({ payload: event }) => {
|
||||
if (event.payload.type === 'prompt_text_request') {
|
||||
const value = await showPrompt(event.payload);
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
windowContext: event.windowContext,
|
||||
payload: {
|
||||
type: 'prompt_text_response',
|
||||
value,
|
||||
},
|
||||
};
|
||||
await emit(event.id, result);
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@ import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { Plugin } from '@yaakapp-internal/models';
|
||||
import { pluginsAtom } from '@yaakapp-internal/models';
|
||||
import type { PluginVersion } from '@yaakapp-internal/plugins';
|
||||
import { checkPluginUpdates, installPlugin, searchPlugins } from '@yaakapp-internal/plugins';
|
||||
import {
|
||||
checkPluginUpdates,
|
||||
installPlugin,
|
||||
searchPlugins,
|
||||
uninstallPlugin,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import type { PluginUpdatesResponse } from '@yaakapp-internal/plugins/bindings/gen_api';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
@@ -11,8 +16,10 @@ import { useDebouncedValue } from '../../hooks/useDebouncedValue';
|
||||
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||
import { useRefreshPlugins } from '../../hooks/usePlugins';
|
||||
import { useUninstallPlugin } from '../../hooks/useUninstallPlugin';
|
||||
import { showConfirmDelete } from '../../lib/confirm';
|
||||
import { minPromiseMillis } from '../../lib/minPromiseMillis';
|
||||
import { Button } from '../core/Button';
|
||||
import { CountBadge } from '../core/CountBadge';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { Link } from '../core/Link';
|
||||
@@ -26,6 +33,7 @@ import { SelectFile } from '../SelectFile';
|
||||
|
||||
export function SettingsPlugins() {
|
||||
const [directory, setDirectory] = React.useState<string | null>(null);
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
const createPlugin = useInstallPlugin();
|
||||
const refreshPlugins = useRefreshPlugins();
|
||||
const [tab, setTab] = useState<string>();
|
||||
@@ -39,7 +47,11 @@ export function SettingsPlugins() {
|
||||
tabListClassName="!-ml-3"
|
||||
tabs={[
|
||||
{ label: 'Marketplace', value: 'search' },
|
||||
{ label: 'Installed', value: 'installed' },
|
||||
{
|
||||
label: 'Installed',
|
||||
value: 'installed',
|
||||
rightSlot: <CountBadge count={plugins.length} />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TabContent value="search">
|
||||
@@ -103,32 +115,36 @@ function PluginTableRow({
|
||||
updates: PluginUpdatesResponse | null;
|
||||
}) {
|
||||
const pluginInfo = usePluginInfo(plugin.id);
|
||||
const uninstallPlugin = useUninstallPlugin();
|
||||
const latestVersion = updates?.plugins.find((u) => u.name === pluginInfo.data?.name)?.version;
|
||||
const installPluginMutation = useMutation({
|
||||
mutationKey: ['install_plugin', plugin.id],
|
||||
mutationFn: (name: string) => installPlugin(name, null),
|
||||
});
|
||||
if (pluginInfo.data == null) return null;
|
||||
|
||||
const displayName = pluginInfo.data?.displayName ?? 'Unknown';
|
||||
const uninstallPluginMutation = usePromptUninstall(plugin.id, displayName);
|
||||
|
||||
if (pluginInfo.isPending) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className="font-semibold">
|
||||
{plugin.url ? (
|
||||
<Link noUnderline href={plugin.url}>
|
||||
{pluginInfo.data.displayName}
|
||||
{displayName}
|
||||
</Link>
|
||||
) : (
|
||||
pluginInfo.data.displayName
|
||||
displayName
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<InlineCode>{pluginInfo.data?.version}</InlineCode>
|
||||
<InlineCode>{pluginInfo.data?.version ?? 'n/a'}</InlineCode>
|
||||
</TableCell>
|
||||
<TableCell className="w-full text-text-subtle">{pluginInfo.data.description}</TableCell>
|
||||
<TableCell>
|
||||
<HStack>
|
||||
{latestVersion != null && (
|
||||
<HStack justifyContent="end">
|
||||
{pluginInfo.data && latestVersion != null && (
|
||||
<Button
|
||||
variant="border"
|
||||
color="success"
|
||||
@@ -140,14 +156,17 @@ function PluginTableRow({
|
||||
Update
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="trash"
|
||||
<Button
|
||||
size="xs"
|
||||
title="Uninstall plugin"
|
||||
variant="border"
|
||||
isLoading={uninstallPluginMutation.isPending}
|
||||
onClick={async () => {
|
||||
uninstallPlugin.mutate({ pluginId: plugin.id, name: pluginInfo.data.displayName });
|
||||
uninstallPluginMutation.mutate();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
Uninstall
|
||||
</Button>
|
||||
</HStack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -173,7 +192,7 @@ function PluginSearch() {
|
||||
defaultValue={query}
|
||||
/>
|
||||
</HStack>
|
||||
<div className="w-full h-full overflow-auto">
|
||||
<div className="w-full h-full">
|
||||
{results.data == null ? (
|
||||
<EmptyStateText>
|
||||
<LoadingIcon size="xl" className="text-text-subtlest" />
|
||||
@@ -186,7 +205,6 @@ function PluginSearch() {
|
||||
<TableRow>
|
||||
<TableHeaderCell>Name</TableHeaderCell>
|
||||
<TableHeaderCell>Version</TableHeaderCell>
|
||||
<TableHeaderCell>Description</TableHeaderCell>
|
||||
<TableHeaderCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -201,11 +219,8 @@ function PluginSearch() {
|
||||
<TableCell>
|
||||
<InlineCode>{plugin.version}</InlineCode>
|
||||
</TableCell>
|
||||
<TableCell className="w-full text-text-subtle">
|
||||
{plugin.description ?? 'n/a'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<InstallPluginButton plugin={plugin} />
|
||||
<TableCell className="w-[6rem]">
|
||||
<InstallPluginButton pluginVersion={plugin} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
@@ -217,12 +232,12 @@ function PluginSearch() {
|
||||
);
|
||||
}
|
||||
|
||||
function InstallPluginButton({ plugin }: { plugin: PluginVersion }) {
|
||||
function InstallPluginButton({ pluginVersion }: { pluginVersion: PluginVersion }) {
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
const uninstallPlugin = useUninstallPlugin();
|
||||
const installed = plugins?.some((p) => p.id === plugin.id);
|
||||
const installed = plugins?.some((p) => p.id === pluginVersion.id);
|
||||
const uninstallPluginMutation = usePromptUninstall(pluginVersion.id, pluginVersion.displayName);
|
||||
const installPluginMutation = useMutation({
|
||||
mutationKey: ['install_plugin', plugin.id],
|
||||
mutationKey: ['install_plugin', pluginVersion.id],
|
||||
mutationFn: (pv: PluginVersion) => installPlugin(pv.name, null),
|
||||
});
|
||||
|
||||
@@ -230,14 +245,14 @@ function InstallPluginButton({ plugin }: { plugin: PluginVersion }) {
|
||||
<Button
|
||||
size="xs"
|
||||
variant="border"
|
||||
color={installed ? 'secondary' : 'primary'}
|
||||
color={installed ? 'default' : 'primary'}
|
||||
className="ml-auto"
|
||||
isLoading={installPluginMutation.isPending}
|
||||
isLoading={installPluginMutation.isPending || uninstallPluginMutation.isPending}
|
||||
onClick={async () => {
|
||||
if (installed) {
|
||||
uninstallPlugin.mutate({ pluginId: plugin.id, name: plugin.displayName });
|
||||
uninstallPluginMutation.mutate();
|
||||
} else {
|
||||
installPluginMutation.mutate(plugin);
|
||||
installPluginMutation.mutate(pluginVersion);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -267,7 +282,6 @@ function InstalledPlugins() {
|
||||
<TableRow>
|
||||
<TableHeaderCell>Name</TableHeaderCell>
|
||||
<TableHeaderCell>Version</TableHeaderCell>
|
||||
<TableHeaderCell>Description</TableHeaderCell>
|
||||
<TableHeaderCell />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -279,3 +293,24 @@ function InstalledPlugins() {
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
function usePromptUninstall(pluginId: string, name: string) {
|
||||
return useMutation({
|
||||
mutationKey: ['uninstall_plugin', pluginId],
|
||||
mutationFn: async () => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'uninstall-plugin-' + pluginId,
|
||||
title: 'Uninstall Plugin',
|
||||
confirmText: 'Uninstall',
|
||||
description: (
|
||||
<>
|
||||
Permanently uninstall <InlineCode>{name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (confirmed) {
|
||||
await minPromiseMillis(uninstallPlugin(pluginId), 700);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useRef } from 'react';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
|
||||
import { useExportData } from '../hooks/useExportData';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { appInfo } from '../lib/appInfo';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { importData } from '../lib/importData';
|
||||
@@ -20,8 +19,6 @@ export function SettingsDropdown() {
|
||||
const checkForUpdates = useCheckForUpdates();
|
||||
const { check } = useLicense();
|
||||
|
||||
useListenToTauriEvent('settings', () => openSettings.mutate(null));
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
ref={dropdownRef}
|
||||
|
||||
@@ -6,11 +6,11 @@ import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { router } from '../lib/router';
|
||||
import { CopyIconButton } from './CopyIconButton';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { Separator } from './core/Separator';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
@@ -20,13 +20,13 @@ import { SyncToFilesystemSetting } from './SyncToFilesystemSetting';
|
||||
import { WorkspaceEncryptionSetting } from './WorkspaceEncryptionSetting';
|
||||
|
||||
interface Props {
|
||||
workspaceId: string | null;
|
||||
workspaceId: string;
|
||||
hide: () => void;
|
||||
tab?: WorkspaceSettingsTab;
|
||||
}
|
||||
|
||||
const TAB_AUTH = 'auth';
|
||||
const TAB_DESCRIPTION = 'description';
|
||||
const TAB_DATA = 'data';
|
||||
const TAB_HEADERS = 'headers';
|
||||
const TAB_GENERAL = 'general';
|
||||
|
||||
@@ -34,9 +34,9 @@ export type WorkspaceSettingsTab =
|
||||
| typeof TAB_AUTH
|
||||
| typeof TAB_HEADERS
|
||||
| typeof TAB_GENERAL
|
||||
| typeof TAB_DESCRIPTION;
|
||||
| typeof TAB_DATA;
|
||||
|
||||
const DEFAULT_TAB: WorkspaceSettingsTab = TAB_DESCRIPTION;
|
||||
const DEFAULT_TAB: WorkspaceSettingsTab = TAB_GENERAL;
|
||||
|
||||
export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
const workspace = useAtomValue(workspacesAtom).find((w) => w.id === workspaceId);
|
||||
@@ -63,25 +63,26 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
layout="horizontal"
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Folder Settings"
|
||||
className="px-1.5 pb-2"
|
||||
className="pt-2 pb-2 pl-3 pr-1"
|
||||
addBorders
|
||||
tabs={[
|
||||
{ value: TAB_DESCRIPTION, label: 'Description' },
|
||||
{ value: TAB_GENERAL, label: 'General' },
|
||||
{
|
||||
value: TAB_GENERAL,
|
||||
label: 'General',
|
||||
value: TAB_DATA,
|
||||
label: 'Directory Sync',
|
||||
},
|
||||
...authTab,
|
||||
...headersTab,
|
||||
]}
|
||||
>
|
||||
<TabContent value={TAB_AUTH} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<TabContent value={TAB_AUTH} className="overflow-y-auto h-full px-4">
|
||||
<HttpAuthenticationEditor model={workspace} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<TabContent value={TAB_HEADERS} className="overflow-y-auto h-full px-4">
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={workspace.id}
|
||||
@@ -90,7 +91,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
stateKey={`headers.${workspace.id}`}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_DESCRIPTION} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-4">
|
||||
<VStack space={4} alignItems="start" className="pb-3 h-full">
|
||||
<PlainInput
|
||||
required
|
||||
@@ -111,23 +112,13 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
onChange={(description) => patchModel(workspace, { description })}
|
||||
heightMode="auto"
|
||||
/>
|
||||
</VStack>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<VStack space={4} alignItems="start" className="pb-3 h-full">
|
||||
<SyncToFilesystemSetting
|
||||
value={{ filePath: workspaceMeta.settingSyncDir }}
|
||||
onCreateNewWorkspace={hide}
|
||||
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||
/>
|
||||
<WorkspaceEncryptionSetting size="xs" />
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<HStack alignItems="center" justifyContent="between" className="w-full">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const didDelete = await deleteModelWithConfirm(workspace);
|
||||
const didDelete = await deleteModelWithConfirm(workspace, {
|
||||
confirmName: workspace.name,
|
||||
});
|
||||
if (didDelete) {
|
||||
hide(); // Only hide if actually deleted workspace
|
||||
await router.navigate({ to: '/' });
|
||||
@@ -139,10 +130,29 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
|
||||
>
|
||||
Delete Workspace
|
||||
</Button>
|
||||
<InlineCode className="select-text cursor-text">{workspaceId}</InlineCode>
|
||||
<InlineCode className="flex gap-1 items-center text-primary pl-2.5">
|
||||
{workspaceId}
|
||||
<CopyIconButton
|
||||
className="opacity-70 !text-primary"
|
||||
size="2xs"
|
||||
iconSize="sm"
|
||||
title="Copy workspace ID"
|
||||
text={workspaceId}
|
||||
/>
|
||||
</InlineCode>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_DATA} className="overflow-y-auto h-full px-4">
|
||||
<VStack space={4} alignItems="start" className="pb-3 h-full">
|
||||
<SyncToFilesystemSetting
|
||||
value={{ filePath: workspaceMeta.settingSyncDir }}
|
||||
onCreateNewWorkspace={hide}
|
||||
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||
/>
|
||||
<WorkspaceEncryptionSetting size="xs" />
|
||||
</VStack>
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,62 @@
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Button } from './Button';
|
||||
import { PlainInput } from './PlainInput';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
export interface ConfirmProps {
|
||||
onHide: () => void;
|
||||
onResult: (result: boolean) => void;
|
||||
confirmText?: string;
|
||||
requireTyping?: string;
|
||||
color?: Color;
|
||||
}
|
||||
|
||||
export function Confirm({ onHide, onResult, confirmText, color = 'primary' }: ConfirmProps) {
|
||||
export function Confirm({
|
||||
onHide,
|
||||
onResult,
|
||||
confirmText,
|
||||
requireTyping,
|
||||
color = 'primary',
|
||||
}: ConfirmProps) {
|
||||
const [confirm, setConfirm] = useState<string>('');
|
||||
const handleHide = () => {
|
||||
onResult(false);
|
||||
onHide();
|
||||
};
|
||||
|
||||
const handleSuccess = () => {
|
||||
onResult(true);
|
||||
onHide();
|
||||
const didConfirm = !requireTyping || confirm === requireTyping;
|
||||
|
||||
const handleSuccess = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (didConfirm) {
|
||||
onResult(true);
|
||||
onHide();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||
<Button color={color} onClick={handleSuccess}>
|
||||
{confirmText ?? 'Confirm'}
|
||||
</Button>
|
||||
<Button onClick={handleHide} variant="border">
|
||||
Cancel
|
||||
</Button>
|
||||
</HStack>
|
||||
<form className="flex flex-col" onSubmit={handleSuccess}>
|
||||
{requireTyping && (
|
||||
<PlainInput
|
||||
autoFocus
|
||||
onChange={setConfirm}
|
||||
label={
|
||||
<>
|
||||
Type <strong>{requireTyping}</strong> to confirm
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||
<Button type="submit" color={color} disabled={!didConfirm}>
|
||||
{confirmText ?? 'Confirm'}
|
||||
</Button>
|
||||
<Button onClick={handleHide} variant="border">
|
||||
Cancel
|
||||
</Button>
|
||||
</HStack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ export function Dialog({
|
||||
'border border-border-subtle shadow-lg shadow-[rgba(0,0,0,0.1)]',
|
||||
'min-h-[10rem]',
|
||||
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-5rem)]',
|
||||
size === 'sm' && 'w-[28rem]',
|
||||
size === 'md' && 'w-[45rem]',
|
||||
size === 'lg' && 'w-[65rem]',
|
||||
size === 'sm' && 'w-[30rem]',
|
||||
size === 'md' && 'w-[50rem]',
|
||||
size === 'lg' && 'w-[70rem]',
|
||||
size === 'full' && 'w-[100vw] h-[100vh]',
|
||||
size === 'dynamic' && 'min-w-[20rem] max-w-[100vw]',
|
||||
)}
|
||||
|
||||
@@ -28,7 +28,7 @@ export function Link({ href, children, noUnderline, className, ...other }: Props
|
||||
rel="noopener noreferrer"
|
||||
className={classNames(
|
||||
className,
|
||||
'pr-4 inline-flex items-center hover:underline',
|
||||
'pr-4 inline-flex items-center hover:underline group',
|
||||
!noUnderline && 'underline',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
@@ -36,8 +36,8 @@ export function Link({ href, children, noUnderline, className, ...other }: Props
|
||||
}}
|
||||
{...other}
|
||||
>
|
||||
<span>{children}</span>
|
||||
<Icon className="inline absolute right-0.5 top-[0.3em]" size="xs" icon="external_link" />
|
||||
<span className="pr-0.5">{children}</span>
|
||||
<Icon className="inline absolute right-0.5 top-[0.3em] opacity-70 group-hover:opacity-100" size="xs" icon="external_link" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TableCell({ children, className }: { children: ReactNode; classN
|
||||
<td
|
||||
className={classNames(
|
||||
className,
|
||||
'py-2 [&:not(:first-child)]:pl-4 text-left w-0 whitespace-nowrap',
|
||||
'py-2 [&:not(:first-child)]:pl-4 text-left whitespace-nowrap',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -57,7 +57,7 @@ export function TableHeaderCell({
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<th className={classNames(className, 'py-2 [&:not(:first-child)]:pl-4 text-left w-0 text-text-subtle')}>
|
||||
<th className={classNames(className, 'py-2 [&:not(:first-child)]:pl-4 text-left text-text-subtle')}>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
|
||||
@@ -84,7 +84,7 @@ export function Tabs({
|
||||
tabListClassName,
|
||||
addBorders && '!-ml-1',
|
||||
'flex items-center hide-scrollbars mb-2',
|
||||
layout === 'horizontal' && 'h-full overflow-auto pt-1 px-2',
|
||||
layout === 'horizontal' && 'h-full overflow-auto px-2',
|
||||
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
|
||||
// Give space for button focus states within overflow boundary.
|
||||
layout === 'vertical' && 'py-1 -ml-5 pl-3 pr-1',
|
||||
@@ -92,7 +92,7 @@ export function Tabs({
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
layout === 'horizontal' && 'flex flex-col gap-1 w-full mt-1 pb-3 mb-auto',
|
||||
layout === 'horizontal' && 'flex flex-col gap-1 w-full pb-3 mb-auto',
|
||||
layout === 'vertical' && 'flex flex-row flex-shrink-0 gap-2 w-full',
|
||||
)}
|
||||
>
|
||||
@@ -104,8 +104,11 @@ export function Tabs({
|
||||
addBorders && 'border',
|
||||
isActive ? 'text-text' : 'text-text-subtle hover:text-text',
|
||||
isActive && addBorders
|
||||
? 'border-border-subtle bg-surface-active'
|
||||
: 'border-transparent',
|
||||
? 'border-surface-active bg-surface-active'
|
||||
: layout === 'vertical'
|
||||
? 'border-border-subtle'
|
||||
: 'border-transparent',
|
||||
layout === 'horizontal' && 'flex justify-between',
|
||||
);
|
||||
|
||||
if ('options' in t) {
|
||||
@@ -121,7 +124,7 @@ export function Tabs({
|
||||
>
|
||||
<button
|
||||
onClick={isActive ? undefined : () => onChangeValue(t.value)}
|
||||
className={btnClassName}
|
||||
className={classNames(btnClassName)}
|
||||
>
|
||||
{option && 'shortLabel' in option && option.shortLabel
|
||||
? option.shortLabel
|
||||
|
||||
Reference in New Issue
Block a user