mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-22 17:39:12 +01:00
Git support (#143)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
@@ -9,16 +10,7 @@ import { LoadingIcon } from './LoadingIcon';
|
||||
|
||||
export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color' | 'onChange'> & {
|
||||
innerClassName?: string;
|
||||
color?:
|
||||
| 'custom'
|
||||
| 'default'
|
||||
| 'secondary'
|
||||
| 'primary'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'notice'
|
||||
| 'warning'
|
||||
| 'danger';
|
||||
color?: Color | 'custom' | 'default';
|
||||
variant?: 'border' | 'solid';
|
||||
isLoading?: boolean;
|
||||
size?: '2xs' | 'xs' | 'sm' | 'md';
|
||||
@@ -59,6 +51,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
const hotkeyTrigger = useFormattedHotkey(hotkeyAction ?? null)?.join('');
|
||||
const fullTitle = hotkeyTrigger ? `${title ?? ''} ${hotkeyTrigger}`.trim() : title;
|
||||
|
||||
if (isLoading) {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
const classes = classNames(
|
||||
className,
|
||||
'x-theme-button',
|
||||
@@ -110,7 +106,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
ref={buttonRef}
|
||||
type={type}
|
||||
className={classes}
|
||||
disabled={disabled || isLoading}
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
onClick?.(e);
|
||||
if (event != null) {
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface CheckboxProps {
|
||||
disabled?: boolean;
|
||||
inputWrapperClassName?: string;
|
||||
hideLabel?: boolean;
|
||||
fullWidth?: boolean;
|
||||
event?: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +24,7 @@ export function Checkbox({
|
||||
disabled,
|
||||
title,
|
||||
hideLabel,
|
||||
fullWidth,
|
||||
event,
|
||||
}: CheckboxProps) {
|
||||
return (
|
||||
@@ -52,7 +54,9 @@ export function Checkbox({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span className={classNames(disabled && 'opacity-disabled')}>{!hideLabel && title}</span>
|
||||
<div className={classNames(fullWidth && 'w-full', disabled && 'opacity-disabled')}>
|
||||
{!hideLabel && title}
|
||||
</div>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import type { ButtonProps } from './Button';
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import { Button } from './Button';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
export interface ConfirmProps {
|
||||
onHide: () => void;
|
||||
onResult: (result: boolean) => void;
|
||||
variant?: 'delete' | 'confirm';
|
||||
confirmText?: string;
|
||||
color?: Color;
|
||||
}
|
||||
|
||||
const colors: Record<NonNullable<ConfirmProps['variant']>, ButtonProps['color']> = {
|
||||
delete: 'danger',
|
||||
confirm: 'primary',
|
||||
};
|
||||
|
||||
const confirmButtonTexts: Record<NonNullable<ConfirmProps['variant']>, string> = {
|
||||
delete: 'Delete',
|
||||
confirm: 'Confirm',
|
||||
};
|
||||
|
||||
export function Confirm({ onHide, onResult, confirmText, variant = 'confirm' }: ConfirmProps) {
|
||||
export function Confirm({ onHide, onResult, confirmText, color = 'primary' }: ConfirmProps) {
|
||||
const handleHide = () => {
|
||||
onResult(false);
|
||||
onHide();
|
||||
@@ -32,8 +22,8 @@ export function Confirm({ onHide, onResult, confirmText, variant = 'confirm' }:
|
||||
|
||||
return (
|
||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||
<Button color={colors[variant]} onClick={handleSuccess}>
|
||||
{confirmText ?? confirmButtonTexts[variant]}
|
||||
<Button color={color} onClick={handleSuccess}>
|
||||
{confirmText ?? 'Confirm'}
|
||||
</Button>
|
||||
<Button onClick={handleHide} variant="border">
|
||||
Cancel
|
||||
|
||||
@@ -36,17 +36,23 @@ import { HotKey } from './HotKey';
|
||||
import { Icon } from './Icon';
|
||||
import { Separator } from './Separator';
|
||||
import { HStack, VStack } from './Stacks';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
|
||||
export type DropdownItemSeparator = {
|
||||
type: 'separator';
|
||||
label?: string;
|
||||
label?: ReactNode;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemContent = {
|
||||
type: 'content';
|
||||
label?: ReactNode;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
export type DropdownItemDefault = {
|
||||
type?: 'default';
|
||||
label: ReactNode;
|
||||
keepOpen?: boolean;
|
||||
hotKeyAction?: HotkeyAction;
|
||||
hotKeyLabelOnly?: boolean;
|
||||
color?: 'default' | 'danger' | 'info' | 'warning' | 'notice';
|
||||
@@ -54,10 +60,11 @@ export type DropdownItemDefault = {
|
||||
hidden?: boolean;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
onSelect?: () => void;
|
||||
waitForOnSelect?: boolean;
|
||||
onSelect?: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
export type DropdownItem = DropdownItemDefault | DropdownItemSeparator;
|
||||
export type DropdownItem = DropdownItemDefault | DropdownItemSeparator | DropdownItemContent;
|
||||
|
||||
export interface DropdownProps {
|
||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
||||
@@ -374,14 +381,20 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
if (i.type !== 'separator' && !i.keepOpen) {
|
||||
handleClose();
|
||||
}
|
||||
async (item: DropdownItem) => {
|
||||
if (!('onSelect' in item) || !item.onSelect) return;
|
||||
setSelectedIndex(null);
|
||||
if (i.type !== 'separator' && typeof i.onSelect === 'function') {
|
||||
i.onSelect();
|
||||
|
||||
const promise = item.onSelect();
|
||||
if (item.waitForOnSelect) {
|
||||
try {
|
||||
await promise;
|
||||
} catch {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
handleClose();
|
||||
},
|
||||
[handleClose, setSelectedIndex],
|
||||
);
|
||||
@@ -391,10 +404,10 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
close: handleClose,
|
||||
prev: handlePrev,
|
||||
next: handleNext,
|
||||
select() {
|
||||
async select() {
|
||||
const item = items[selectedIndexRef.current ?? -1] ?? null;
|
||||
if (!item) return;
|
||||
handleSelect(item);
|
||||
await handleSelect(item);
|
||||
},
|
||||
};
|
||||
}, [handleClose, handleNext, handlePrev, handleSelect, items]);
|
||||
@@ -466,6 +479,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
{items.map(
|
||||
(item, i) =>
|
||||
item.type !== 'separator' &&
|
||||
item.type !== 'content' &&
|
||||
!item.hotKeyLabelOnly &&
|
||||
item.hotKeyAction && (
|
||||
<MenuItemHotKey
|
||||
@@ -519,7 +533,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
space={2}
|
||||
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded font-mono h-xs"
|
||||
>
|
||||
<Icon icon="search" size="xs" className="text-text-subtle" />
|
||||
<Icon icon="search" size="xs" />
|
||||
<div className="text">{filter}</div>
|
||||
</HStack>
|
||||
)}
|
||||
@@ -537,6 +551,13 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
</Separator>
|
||||
);
|
||||
}
|
||||
if (item.type === 'content') {
|
||||
return (
|
||||
<div key={i} className={classNames('my-1.5 mx-2 max-w-xs')}>
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
focused={i === selectedIndex}
|
||||
@@ -559,13 +580,19 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
interface MenuItemProps {
|
||||
className?: string;
|
||||
item: DropdownItemDefault;
|
||||
onSelect: (item: DropdownItemDefault) => void;
|
||||
onSelect: (item: DropdownItemDefault) => Promise<void>;
|
||||
onFocus: (item: DropdownItemDefault) => void;
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: MenuItemProps) {
|
||||
const handleClick = useCallback(() => onSelect?.(item), [item, onSelect]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleClick = useCallback(async () => {
|
||||
if (item.waitForOnSelect) setIsLoading(true);
|
||||
await onSelect?.(item);
|
||||
if (item.waitForOnSelect) setIsLoading(false);
|
||||
}, [item, onSelect]);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(e: ReactFocusEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation(); // Don't trigger focus on any parents
|
||||
@@ -598,7 +625,11 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
onClick={handleClick}
|
||||
justify="start"
|
||||
leftSlot={
|
||||
item.leftSlot && <div className="pr-2 flex justify-start opacity-70">{item.leftSlot}</div>
|
||||
(isLoading || item.leftSlot) && (
|
||||
<div className={classNames('pr-2 flex justify-start opacity-70')}>
|
||||
{isLoading ? <LoadingIcon /> : item.leftSlot}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
rightSlot={rightSlot && <div className="ml-auto pl-3">{rightSlot}</div>}
|
||||
innerClassName="!text-left"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import * as lucide from 'lucide-react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
@@ -14,9 +15,11 @@ const icons = {
|
||||
arrow_big_up_dash: lucide.ArrowBigUpDashIcon,
|
||||
arrow_down: lucide.ArrowDownIcon,
|
||||
arrow_down_to_dot: lucide.ArrowDownToDotIcon,
|
||||
arrow_down_to_line: lucide.ArrowDownToLineIcon,
|
||||
arrow_up: lucide.ArrowUpIcon,
|
||||
arrow_up_down: lucide.ArrowUpDownIcon,
|
||||
arrow_up_from_dot: lucide.ArrowUpFromDotIcon,
|
||||
arrow_up_from_line: lucide.ArrowUpFromLineIcon,
|
||||
badge_check: lucide.BadgeCheckIcon,
|
||||
box: lucide.BoxIcon,
|
||||
cake: lucide.CakeIcon,
|
||||
@@ -42,11 +45,14 @@ const icons = {
|
||||
filter: lucide.FilterIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folder: lucide.FolderIcon,
|
||||
folder_git: lucide.FolderGitIcon,
|
||||
folder_input: lucide.FolderInputIcon,
|
||||
folder_open: lucide.FolderOpenIcon,
|
||||
folder_output: lucide.FolderOutputIcon,
|
||||
folder_symlink: lucide.FolderSymlinkIcon,
|
||||
folder_sync: lucide.FolderSyncIcon,
|
||||
git_branch: lucide.GitBranchIcon,
|
||||
git_branch_plus: lucide.GitBranchPlusIcon,
|
||||
git_commit: lucide.GitCommitIcon,
|
||||
git_commit_vertical: lucide.GitCommitVerticalIcon,
|
||||
git_pull_request: lucide.GitPullRequestIcon,
|
||||
@@ -63,6 +69,7 @@ const icons = {
|
||||
left_panel_visible: lucide.PanelLeftCloseIcon,
|
||||
lock: lucide.LockIcon,
|
||||
magic_wand: lucide.Wand2Icon,
|
||||
merge: lucide.MergeIcon,
|
||||
minus: lucide.MinusIcon,
|
||||
minus_circle: lucide.MinusCircleIcon,
|
||||
moon: lucide.MoonIcon,
|
||||
@@ -86,6 +93,8 @@ const icons = {
|
||||
unpin: lucide.PinOffIcon,
|
||||
update: lucide.RefreshCcwIcon,
|
||||
upload: lucide.UploadIcon,
|
||||
variable: lucide.VariableIcon,
|
||||
wrench: lucide.WrenchIcon,
|
||||
x: lucide.XIcon,
|
||||
_unknown: lucide.ShieldAlertIcon,
|
||||
|
||||
@@ -98,22 +107,38 @@ export interface IconProps {
|
||||
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
spin?: boolean;
|
||||
title?: string;
|
||||
color?: Color | 'custom' | 'default';
|
||||
}
|
||||
|
||||
export const Icon = memo(function Icon({ icon, spin, size = 'md', className, title }: IconProps) {
|
||||
export const Icon = memo(function Icon({
|
||||
icon,
|
||||
color = 'default',
|
||||
spin,
|
||||
size = 'md',
|
||||
className,
|
||||
title,
|
||||
}: IconProps) {
|
||||
const Component = icons[icon] ?? icons._unknown;
|
||||
return (
|
||||
<Component
|
||||
title={title}
|
||||
className={classNames(
|
||||
className,
|
||||
'text-inherit flex-shrink-0',
|
||||
'flex-shrink-0',
|
||||
size === 'xl' && 'h-6 w-6',
|
||||
size === 'lg' && 'h-5 w-5',
|
||||
size === 'md' && 'h-4 w-4',
|
||||
size === 'sm' && 'h-3.5 w-3.5',
|
||||
size === 'xs' && 'h-3 w-3',
|
||||
size === '2xs' && 'h-2.5 w-2.5',
|
||||
color === 'default' && 'inherit',
|
||||
color === 'danger' && 'text-danger',
|
||||
color === 'warning' && 'text-warning',
|
||||
color === 'notice' && 'text-notice',
|
||||
color === 'info' && 'text-info',
|
||||
color === 'success' && 'text-success',
|
||||
color === 'primary' && 'text-primary',
|
||||
color === 'secondary' && 'text-secondary',
|
||||
spin && 'animate-spin',
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ export type IconButtonProps = IconProps &
|
||||
showConfirm?: boolean;
|
||||
iconClassName?: string;
|
||||
iconSize?: IconProps['size'];
|
||||
iconColor?: IconProps['color'];
|
||||
title: string;
|
||||
showBadge?: boolean;
|
||||
};
|
||||
@@ -29,6 +30,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
size = 'md',
|
||||
iconSize,
|
||||
showBadge,
|
||||
iconColor,
|
||||
...props
|
||||
}: IconButtonProps,
|
||||
ref,
|
||||
@@ -47,7 +49,7 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
ref={ref}
|
||||
aria-hidden={icon === 'empty'}
|
||||
disabled={icon === 'empty'}
|
||||
tabIndex={tabIndex ?? icon === 'empty' ? -1 : undefined}
|
||||
tabIndex={(tabIndex ?? icon === 'empty') ? -1 : undefined}
|
||||
onClick={handleClick}
|
||||
innerClassName="flex items-center justify-center"
|
||||
size={size}
|
||||
@@ -56,8 +58,6 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
className,
|
||||
'group/button relative flex-shrink-0',
|
||||
'!px-0',
|
||||
color === 'custom' && 'text-text-subtle',
|
||||
color === 'default' && 'text-text-subtle',
|
||||
size === 'md' && 'w-md',
|
||||
size === 'sm' && 'w-sm',
|
||||
size === 'xs' && 'w-xs',
|
||||
@@ -74,11 +74,11 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(functio
|
||||
size={iconSize}
|
||||
icon={confirmed ? 'check' : icon}
|
||||
spin={spin}
|
||||
color={confirmed ? 'success' : iconColor}
|
||||
className={classNames(
|
||||
iconClassName,
|
||||
'group-hover/button:text',
|
||||
'group-hover/button:text-text',
|
||||
props.disabled && 'opacity-70',
|
||||
confirmed && 'text-green-600',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
@@ -46,6 +46,7 @@ export type InputProps = Pick<
|
||||
required?: boolean;
|
||||
wrapLines?: boolean;
|
||||
multiLine?: boolean;
|
||||
fullHeight?: boolean;
|
||||
stateKey: EditorProps['stateKey'];
|
||||
};
|
||||
|
||||
@@ -56,6 +57,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
inputWrapperClassName,
|
||||
defaultValue,
|
||||
forceUpdateKey,
|
||||
fullHeight,
|
||||
hideLabel,
|
||||
label,
|
||||
labelClassName,
|
||||
@@ -148,8 +150,9 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'pointer-events-auto', // Just in case we're placing in disabled parent
|
||||
'w-full',
|
||||
fullHeight && 'h-full',
|
||||
labelPosition === 'left' && 'flex items-center gap-2',
|
||||
labelPosition === 'top' && 'flex-row gap-0.5',
|
||||
)}
|
||||
@@ -166,6 +169,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
alignItems="stretch"
|
||||
className={classNames(
|
||||
containerClassName,
|
||||
fullHeight && 'h-full',
|
||||
'x-theme-input',
|
||||
'relative w-full rounded-md text',
|
||||
'border',
|
||||
@@ -182,6 +186,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
className={classNames(
|
||||
inputWrapperClassName,
|
||||
'w-full min-w-0 px-2',
|
||||
fullHeight && 'h-full',
|
||||
leftSlot && 'pl-0.5 -ml-2',
|
||||
rightSlot && 'pr-0.5 -mr-2',
|
||||
)}
|
||||
@@ -218,8 +223,11 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
<IconButton
|
||||
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
||||
size="xs"
|
||||
className={classNames("mr-0.5 group/obscure !h-auto my-0.5", disabled && 'opacity-disabled')}
|
||||
iconClassName="text-text-subtle group-hover/obscure:text"
|
||||
className={classNames(
|
||||
'mr-0.5 group/obscure !h-auto my-0.5',
|
||||
disabled && 'opacity-disabled',
|
||||
)}
|
||||
iconClassName="group-hover/obscure:text"
|
||||
iconSize="sm"
|
||||
icon={obscured ? 'eye' : 'eye_closed'}
|
||||
onClick={() => setObscured((o) => !o)}
|
||||
|
||||
@@ -104,7 +104,7 @@ export const JsonAttributeTree = ({
|
||||
icon="chevron_right"
|
||||
className={classNames(
|
||||
'left-0 absolute transition-transform flex items-center',
|
||||
'text-text-subtlest group-hover:text-text-subtle',
|
||||
'group-hover:text-text-subtle',
|
||||
isExpanded ? 'rotate-90' : '',
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const PairOrBulkEditor = forwardRef<PairEditorRef, Props>(function PairOr
|
||||
title={useBulk ? 'Enable form edit' : 'Enable bulk edit'}
|
||||
className={classNames(
|
||||
'transition-opacity opacity-0 group-hover:opacity-80 hover:!opacity-100 shadow',
|
||||
'bg-surface text-text-subtle hover:text group-hover/wrapper:opacity-100',
|
||||
'bg-surface hover:text group-hover/wrapper:opacity-100',
|
||||
)}
|
||||
onClick={() => setUseBulk((b) => !b)}
|
||||
icon={useBulk ? 'table' : 'file_code'}
|
||||
|
||||
@@ -148,7 +148,7 @@ export function PlainInput({
|
||||
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
||||
size="xs"
|
||||
className="mr-0.5 group/obscure !h-auto my-0.5"
|
||||
iconClassName="text-text-subtle group-hover/obscure:text"
|
||||
iconClassName="group-hover/obscure:text"
|
||||
iconSize="sm"
|
||||
icon={obscured ? 'eye' : 'eye_closed'}
|
||||
onClick={() => setObscured((o) => !o)}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function SegmentedControl<T extends string>({ value, onChange, options, n
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="solid"
|
||||
color={isActive ? "secondary" : "default"}
|
||||
color={isActive ? "secondary" : undefined}
|
||||
role="radio"
|
||||
event={{ id: name, value: String(o.value) }}
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
|
||||
64
src-web/components/core/Table.tsx
Normal file
64
src-web/components/core/Table.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
export function Table({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableBody({ children }: { children: ReactNode }) {
|
||||
return <tbody className="divide-y divide-surface-highlight">{children}</tbody>;
|
||||
}
|
||||
|
||||
export function TableHead({ children }: { children: ReactNode }) {
|
||||
return <thead>{children}</thead>;
|
||||
}
|
||||
|
||||
export function TableRow({ children }: { children: ReactNode }) {
|
||||
return <tr>{children}</tr>;
|
||||
}
|
||||
|
||||
export function TableCell({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<td
|
||||
className={classNames(
|
||||
className,
|
||||
'py-2 [&:not(:first-child)]:pl-4 text-left w-0 whitespace-nowrap',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
export function TruncatedWideTableCell({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<TableCell className={classNames(className, 'w-full relative')}>
|
||||
<div className="absolute inset-0 py-2 truncate">{children}</div>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableHeaderCell({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<th className={classNames(className, 'py-2 [&:not(:first-child)]:pl-4 text-left w-0')}>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export function Tabs({
|
||||
<Icon
|
||||
size="sm"
|
||||
icon="chevron_down"
|
||||
className={classNames('ml-1', isActive ? 'text-text-subtle' : 'opacity-50')}
|
||||
className={classNames('ml-1', !isActive && 'opacity-50')}
|
||||
/>
|
||||
</button>
|
||||
</RadioDropdown>
|
||||
|
||||
@@ -55,14 +55,14 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
||||
<div
|
||||
className={classNames(
|
||||
`x-theme-toast x-theme-toast--${color}`,
|
||||
'pointer-events-auto overflow-hidden break-all',
|
||||
'pointer-events-auto overflow-hidden',
|
||||
'relative pointer-events-auto bg-surface text-text rounded-lg',
|
||||
'border border-border shadow-lg w-[25rem]',
|
||||
'grid grid-cols-[1fr_auto]',
|
||||
)}
|
||||
>
|
||||
<div className="px-3 py-3 flex items-start gap-2 w-full">
|
||||
{toastIcon && <Icon icon={toastIcon} className="mt-1 text-text-subtle" />}
|
||||
{toastIcon && <Icon icon={toastIcon} color={color} className="mt-1" />}
|
||||
<VStack space={2} className="w-full">
|
||||
<div>{children}</div>
|
||||
{action?.({ hide: onClose })}
|
||||
|
||||
Reference in New Issue
Block a user