mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-10 11:13:48 +02:00
Run oxfmt across repo, add format script and docs
Add .oxfmtignore to skip generated bindings and wasm-pack output. Add npm format script, update DEVELOPMENT.md for Vite+ toolchain, and format all non-generated files with oxfmt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from '@dnd-kit/core';
|
||||
import type { DragEndEvent, DragMoveEvent, DragStartEvent } from "@dnd-kit/core";
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
useDroppable,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode, Ref } from 'react';
|
||||
} from "@dnd-kit/core";
|
||||
import classNames from "classnames";
|
||||
import type { ReactNode, Ref } from "react";
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
@@ -20,17 +20,17 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useKeyValue } from '../../../hooks/useKeyValue';
|
||||
import { fireAndForget } from '../../../lib/fireAndForget';
|
||||
import { computeSideForDragMove } from '../../../lib/dnd';
|
||||
import { DropMarker } from '../../DropMarker';
|
||||
import { ErrorBoundary } from '../../ErrorBoundary';
|
||||
import type { ButtonProps } from '../Button';
|
||||
import { Button } from '../Button';
|
||||
import { Icon } from '../Icon';
|
||||
import type { RadioDropdownProps } from '../RadioDropdown';
|
||||
import { RadioDropdown } from '../RadioDropdown';
|
||||
} from "react";
|
||||
import { useKeyValue } from "../../../hooks/useKeyValue";
|
||||
import { fireAndForget } from "../../../lib/fireAndForget";
|
||||
import { computeSideForDragMove } from "../../../lib/dnd";
|
||||
import { DropMarker } from "../../DropMarker";
|
||||
import { ErrorBoundary } from "../../ErrorBoundary";
|
||||
import type { ButtonProps } from "../Button";
|
||||
import { Button } from "../Button";
|
||||
import { Icon } from "../Icon";
|
||||
import type { RadioDropdownProps } from "../RadioDropdown";
|
||||
import { RadioDropdown } from "../RadioDropdown";
|
||||
|
||||
export type TabItem =
|
||||
| {
|
||||
@@ -42,7 +42,7 @@ export type TabItem =
|
||||
}
|
||||
| {
|
||||
value: string;
|
||||
options: Omit<RadioDropdownProps, 'children'>;
|
||||
options: Omit<RadioDropdownProps, "children">;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
};
|
||||
@@ -68,7 +68,7 @@ interface Props {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
addBorders?: boolean;
|
||||
layout?: 'horizontal' | 'vertical';
|
||||
layout?: "horizontal" | "vertical";
|
||||
/** Storage key for persisting tab order and active tab. When provided, enables drag-to-reorder and active tab persistence. */
|
||||
storageKey?: string | string[];
|
||||
/** Key to identify which context this tab belongs to (e.g., request ID). Used for per-context active tab persistence. */
|
||||
@@ -85,7 +85,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
className,
|
||||
tabListClassName,
|
||||
addBorders,
|
||||
layout = 'vertical',
|
||||
layout = "vertical",
|
||||
storageKey,
|
||||
activeTabKey,
|
||||
}: Props,
|
||||
@@ -97,8 +97,8 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
// Use key-value storage for persistence if storageKey is provided
|
||||
// Handle migration from old format (string[]) to new format (TabsStorage)
|
||||
const { value: rawStorage, set: setStorage } = useKeyValue<TabsStorage | string[]>({
|
||||
namespace: 'no_sync',
|
||||
key: storageKey ?? ['tabs', 'default'],
|
||||
namespace: "no_sync",
|
||||
key: storageKey ?? ["tabs", "default"],
|
||||
fallback: { order: [], activeTabs: {} },
|
||||
});
|
||||
|
||||
@@ -203,20 +203,20 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
|
||||
// Update tabs when value changes
|
||||
useEffect(() => {
|
||||
const tabs = ref.current?.querySelectorAll<HTMLDivElement>('[data-tab]');
|
||||
const tabs = ref.current?.querySelectorAll<HTMLDivElement>("[data-tab]");
|
||||
for (const tab of tabs ?? []) {
|
||||
const v = tab.getAttribute('data-tab');
|
||||
const parent = tab.closest('.tabs-container');
|
||||
const v = tab.getAttribute("data-tab");
|
||||
const parent = tab.closest(".tabs-container");
|
||||
if (parent !== ref.current) {
|
||||
// Tab is part of a nested tab container, so ignore it
|
||||
} else if (v === value) {
|
||||
tab.setAttribute('data-state', 'active');
|
||||
tab.setAttribute('aria-hidden', 'false');
|
||||
tab.style.display = 'block';
|
||||
tab.setAttribute("data-state", "active");
|
||||
tab.setAttribute("aria-hidden", "false");
|
||||
tab.style.display = "block";
|
||||
} else {
|
||||
tab.setAttribute('data-state', 'inactive');
|
||||
tab.setAttribute('aria-hidden', 'true');
|
||||
tab.style.display = 'none';
|
||||
tab.setAttribute("data-state", "inactive");
|
||||
tab.setAttribute("aria-hidden", "true");
|
||||
tab.style.display = "none";
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
@@ -241,14 +241,14 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
if (overTab == null) return setHoveredIndex(null);
|
||||
|
||||
// For vertical layout, tabs are arranged horizontally (side-by-side)
|
||||
const orientation = layout === 'vertical' ? 'horizontal' : 'vertical';
|
||||
const orientation = layout === "vertical" ? "horizontal" : "vertical";
|
||||
const side = computeSideForDragMove(overTab.value, e, orientation);
|
||||
|
||||
// If computeSideForDragMove returns null (shouldn't happen but be safe), default to null
|
||||
if (side === null) return setHoveredIndex(null);
|
||||
|
||||
const overIndex = tabs.findIndex((t) => t.value === overId);
|
||||
const hoveredIndex = overIndex + (side === 'before' ? 0 : 1);
|
||||
const hoveredIndex = overIndex + (side === "before" ? 0 : 1);
|
||||
|
||||
setHoveredIndex(hoveredIndex);
|
||||
},
|
||||
@@ -291,7 +291,7 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
const tabButtons = useMemo(() => {
|
||||
const items: ReactNode[] = [];
|
||||
tabs.forEach((t, i) => {
|
||||
if ('hidden' in t && t.hidden) {
|
||||
if ("hidden" in t && t.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,9 +302,9 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
items.push(
|
||||
<div
|
||||
key={`marker-${t.value}`}
|
||||
className={classNames('relative', layout === 'vertical' ? 'w-0' : 'h-0')}
|
||||
className={classNames("relative", layout === "vertical" ? "w-0" : "h-0")}
|
||||
>
|
||||
<DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} />
|
||||
<DropMarker orientation={layout === "vertical" ? "vertical" : "horizontal"} />
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
@@ -331,25 +331,25 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
aria-label={label}
|
||||
className={classNames(
|
||||
tabListClassName,
|
||||
addBorders && layout === 'horizontal' && 'pl-3 -ml-1',
|
||||
addBorders && layout === 'vertical' && 'ml-0 mb-2',
|
||||
'flex items-center hide-scrollbars',
|
||||
layout === 'horizontal' && 'h-full overflow-auto p-2',
|
||||
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
|
||||
addBorders && layout === "horizontal" && "pl-3 -ml-1",
|
||||
addBorders && layout === "vertical" && "ml-0 mb-2",
|
||||
"flex items-center hide-scrollbars",
|
||||
layout === "horizontal" && "h-full overflow-auto p-2",
|
||||
layout === "vertical" && "overflow-x-auto overflow-y-visible ",
|
||||
// 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",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
layout === 'horizontal' && 'flex flex-col w-full pb-3 mb-auto',
|
||||
layout === 'vertical' && 'flex flex-row flex-shrink-0 w-full',
|
||||
layout === "horizontal" && "flex flex-col w-full pb-3 mb-auto",
|
||||
layout === "vertical" && "flex flex-row flex-shrink-0 w-full",
|
||||
)}
|
||||
>
|
||||
{tabButtons}
|
||||
{hoveredIndex === tabs.length && (
|
||||
<div className={classNames('relative', layout === 'vertical' ? 'w-0' : 'h-0')}>
|
||||
<DropMarker orientation={layout === 'vertical' ? 'vertical' : 'horizontal'} />
|
||||
<div className={classNames("relative", layout === "vertical" ? "w-0" : "h-0")}>
|
||||
<DropMarker orientation={layout === "vertical" ? "vertical" : "horizontal"} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -361,10 +361,10 @@ export const Tabs = forwardRef<TabsRef, Props>(function Tabs(
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
className,
|
||||
'tabs-container',
|
||||
'h-full grid',
|
||||
layout === 'horizontal' && 'grid-rows-1 grid-cols-[auto_minmax(0,1fr)]',
|
||||
layout === 'vertical' && 'grid-rows-[auto_minmax(0,1fr)] grid-cols-1',
|
||||
"tabs-container",
|
||||
"h-full grid",
|
||||
layout === "horizontal" && "grid-rows-1 grid-cols-[auto_minmax(0,1fr)]",
|
||||
layout === "vertical" && "grid-rows-[auto_minmax(0,1fr)] grid-cols-1",
|
||||
)}
|
||||
>
|
||||
{reorderable ? (
|
||||
@@ -405,7 +405,7 @@ interface TabButtonProps {
|
||||
tab: TabItem;
|
||||
isActive: boolean;
|
||||
addBorders?: boolean;
|
||||
layout: 'horizontal' | 'vertical';
|
||||
layout: "horizontal" | "vertical";
|
||||
reorderable: boolean;
|
||||
isDragging: boolean;
|
||||
onChangeValue?: (value: string) => void;
|
||||
@@ -448,8 +448,8 @@ function TabButton({
|
||||
);
|
||||
|
||||
const btnProps: Partial<ButtonProps> = {
|
||||
color: 'custom',
|
||||
justify: layout === 'horizontal' ? 'start' : 'center',
|
||||
color: "custom",
|
||||
justify: layout === "horizontal" ? "start" : "center",
|
||||
onClick: isActive
|
||||
? undefined
|
||||
: (e: React.MouseEvent) => {
|
||||
@@ -457,27 +457,27 @@ function TabButton({
|
||||
onChangeValue?.(tab.value);
|
||||
},
|
||||
className: classNames(
|
||||
'flex items-center rounded whitespace-nowrap',
|
||||
'!px-2 ml-[1px]',
|
||||
'outline-none',
|
||||
'ring-none',
|
||||
'focus-visible-or-class:outline-2',
|
||||
addBorders && 'border focus-visible:bg-surface-highlight',
|
||||
isActive ? 'text-text' : 'text-text-subtle',
|
||||
"flex items-center rounded whitespace-nowrap",
|
||||
"!px-2 ml-[1px]",
|
||||
"outline-none",
|
||||
"ring-none",
|
||||
"focus-visible-or-class:outline-2",
|
||||
addBorders && "border focus-visible:bg-surface-highlight",
|
||||
isActive ? "text-text" : "text-text-subtle",
|
||||
isActive && addBorders
|
||||
? 'border-surface-active bg-surface-active'
|
||||
: layout === 'vertical'
|
||||
? 'border-border-subtle'
|
||||
: 'border-transparent',
|
||||
layout === 'horizontal' && 'min-w-[10rem]',
|
||||
isDragging && 'opacity-50',
|
||||
overlay && 'opacity-80',
|
||||
? "border-surface-active bg-surface-active"
|
||||
: layout === "vertical"
|
||||
? "border-border-subtle"
|
||||
: "border-transparent",
|
||||
layout === "horizontal" && "min-w-[10rem]",
|
||||
isDragging && "opacity-50",
|
||||
overlay && "opacity-80",
|
||||
),
|
||||
};
|
||||
|
||||
const buttonContent = (() => {
|
||||
if ('options' in tab) {
|
||||
const option = tab.options.items.find((i) => 'value' in i && i.value === tab.options.value);
|
||||
if ("options" in tab) {
|
||||
const option = tab.options.items.find((i) => "value" in i && i.value === tab.options.value);
|
||||
return (
|
||||
<RadioDropdown
|
||||
key={tab.value}
|
||||
@@ -496,24 +496,24 @@ function TabButton({
|
||||
size="sm"
|
||||
icon="chevron_down"
|
||||
className={classNames(
|
||||
'ml-1',
|
||||
isActive ? 'text-text-subtle' : 'text-text-subtlest',
|
||||
"ml-1",
|
||||
isActive ? "text-text-subtle" : "text-text-subtlest",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{...btnProps}
|
||||
>
|
||||
{option && 'shortLabel' in option && option.shortLabel
|
||||
{option && "shortLabel" in option && option.shortLabel
|
||||
? option.shortLabel
|
||||
: (option?.label ?? 'Unknown')}
|
||||
: (option?.label ?? "Unknown")}
|
||||
</Button>
|
||||
</RadioDropdown>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button leftSlot={tab.leftSlot} rightSlot={tab.rightSlot} {...btnProps}>
|
||||
{'label' in tab && tab.label ? tab.label : tab.value}
|
||||
{"label" in tab && tab.label ? tab.label : tab.value}
|
||||
</Button>
|
||||
);
|
||||
})();
|
||||
@@ -524,7 +524,7 @@ function TabButton({
|
||||
return (
|
||||
<div
|
||||
ref={handleSetWrapperRef}
|
||||
className={classNames('relative', layout === 'vertical' && 'mr-2')}
|
||||
className={classNames("relative", layout === "vertical" && "mr-2")}
|
||||
{...wrapperProps}
|
||||
>
|
||||
{buttonContent}
|
||||
@@ -548,7 +548,7 @@ export const TabContent = memo(function TabContent({
|
||||
<div
|
||||
tabIndex={-1}
|
||||
data-tab={value}
|
||||
className={classNames(className, 'tab-content', 'hidden w-full h-full pt-2')}
|
||||
className={classNames(className, "tab-content", "hidden w-full h-full pt-2")}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -569,14 +569,14 @@ export async function setActiveTab({
|
||||
activeTabKey: string;
|
||||
value: string;
|
||||
}): Promise<void> {
|
||||
const { getKeyValue, setKeyValue } = await import('../../../lib/keyValueStore');
|
||||
const { getKeyValue, setKeyValue } = await import("../../../lib/keyValueStore");
|
||||
const current = getKeyValue<TabsStorage>({
|
||||
namespace: 'no_sync',
|
||||
namespace: "no_sync",
|
||||
key: storageKey,
|
||||
fallback: { order: [], activeTabs: {} },
|
||||
});
|
||||
await setKeyValue({
|
||||
namespace: 'no_sync',
|
||||
namespace: "no_sync",
|
||||
key: storageKey,
|
||||
value: {
|
||||
...current,
|
||||
|
||||
Reference in New Issue
Block a user