diff --git a/packages/core/.oxlintrc.jsonc b/packages/core/.oxlintrc.jsonc
index 10c46209..ef5e146b 100644
--- a/packages/core/.oxlintrc.jsonc
+++ b/packages/core/.oxlintrc.jsonc
@@ -8,6 +8,7 @@
"rules": {
// React 19's JSX Transform handles React imports automatically
"react/react-in-jsx-scope": "off",
+ "no-nested-ternary": "error",
},
"ignorePatterns": ["dist", "node_modules"],
}
diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx
index a8f52d49..13ccf0d2 100644
--- a/packages/core/src/components/csv-importer/CsvImporter.tsx
+++ b/packages/core/src/components/csv-importer/CsvImporter.tsx
@@ -239,11 +239,11 @@ function MappingStep({
>
{/* Status icon */}
- {isMapped ? (
-
- ) : col.required ? (
+ {isMapped && }
+ {!isMapped && col.required && (
- ) : (
+ )}
+ {!isMapped && !col.required && (
)}
|
@@ -741,9 +741,7 @@ export function CsvImporter({
"astw:flex astw:size-7 astw:items-center astw:justify-center astw:rounded-full astw:text-xs astw:font-medium",
step === s
? "astw:bg-primary astw:text-primary-foreground"
- : stepIndex(step) > idx
- ? "astw:bg-primary/20 astw:text-primary"
- : "astw:bg-muted astw:text-muted-foreground",
+ : stepClassForIndex(step, idx),
)}
>
{idx + 1}
@@ -900,3 +898,9 @@ function stepIndex(step: CsvImporterStep): number {
const steps: CsvImporterStep[] = ["upload", "mapping", "review", "complete"];
return steps.indexOf(step);
}
+
+function stepClassForIndex(step: CsvImporterStep, idx: number): string {
+ return stepIndex(step) > idx
+ ? "astw:bg-primary/20 astw:text-primary"
+ : "astw:bg-muted astw:text-muted-foreground";
+}
diff --git a/packages/core/src/components/csv-importer/process-rows.ts b/packages/core/src/components/csv-importer/process-rows.ts
index 02a33b10..4a1a7560 100644
--- a/packages/core/src/components/csv-importer/process-rows.ts
+++ b/packages/core/src/components/csv-importer/process-rows.ts
@@ -1,5 +1,15 @@
import type { CsvCellIssue, CsvColumnMapping, CsvCorrection, CsvSchema, ParsedRow } from "./types";
+function resolveRawValue(
+ correction: CsvCorrection | undefined,
+ colIdx: number | undefined,
+ row: string[],
+): string {
+ if (correction !== undefined) return String(correction.newValue);
+ if (colIdx !== undefined) return row[colIdx];
+ return "";
+}
+
/** Single-pass: validate all cells and build parsed rows for onValidate. */
export function processRows(
rawRows: string[][],
@@ -24,12 +34,7 @@ export function processRows(
const correction = corrections.find(
(c) => c.row === rowIdx && c.columnKey === mapping.columnKey,
);
- const rawValue: string =
- correction !== undefined
- ? String(correction.newValue)
- : colIdx !== undefined
- ? row[colIdx]
- : "";
+ const rawValue = resolveRawValue(correction, colIdx, row);
const column = schema.columns.find((c) => c.key === mapping.columnKey);
if (column?.schema) {
diff --git a/packages/core/src/components/data-table/cell-renderers.tsx b/packages/core/src/components/data-table/cell-renderers.tsx
index b6598462..9f2335e6 100644
--- a/packages/core/src/components/data-table/cell-renderers.tsx
+++ b/packages/core/src/components/data-table/cell-renderers.tsx
@@ -11,6 +11,20 @@ import type {
NumberCellOptions,
} from "./types";
+function resolveDateFormatOptions(format: string): Intl.DateTimeFormatOptions {
+ if (format === "long") return { month: "long", day: "numeric", year: "numeric" };
+ if (format === "datetime") {
+ return {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ };
+ }
+ return { month: "short", day: "numeric", year: "numeric" };
+}
+
const PLACEHOLDER = (
—
@@ -79,7 +93,10 @@ function renderMoney>(
// `maxDecimals` raises the cap above the currency default while keeping the
// minimum at the currency default (e.g. 2 for USD). Lets a JPY column stay
// at 0 decimals while a USD price-detail column shows up to 4.
- const formatOptions: Intl.NumberFormatOptions = { style: "currency", currency };
+ const formatOptions: Intl.NumberFormatOptions = {
+ style: "currency",
+ currency,
+ };
if (options?.maxDecimals != null) {
formatOptions.maximumFractionDigits = options.maxDecimals;
}
@@ -101,18 +118,7 @@ function renderDate(value: unknown, options: DateCellOptions | undefined): React
const date = toDate(value);
if (!date) return PLACEHOLDER;
const format = options?.dateFormat ?? "short";
- const formatOptions: Intl.DateTimeFormatOptions =
- format === "long"
- ? { month: "long", day: "numeric", year: "numeric" }
- : format === "datetime"
- ? {
- month: "short",
- day: "numeric",
- year: "numeric",
- hour: "numeric",
- minute: "2-digit",
- }
- : { month: "short", day: "numeric", year: "numeric" };
+ const formatOptions = resolveDateFormatOptions(format);
return new Intl.DateTimeFormat(options?.locale, formatOptions).format(date);
}
diff --git a/packages/core/src/components/data-table/data-table.tsx b/packages/core/src/components/data-table/data-table.tsx
index 7cd8e6ea..eb3f50a9 100644
--- a/packages/core/src/components/data-table/data-table.tsx
+++ b/packages/core/src/components/data-table/data-table.tsx
@@ -29,6 +29,12 @@ function resolveAlign>(col: Column):
return "left";
}
+function nextSortDirection(current: string | undefined): "Asc" | "Desc" | undefined {
+ if (current === "Asc") return "Desc";
+ if (current === "Desc") return undefined;
+ return "Asc";
+}
+
// =============================================================================
// DataTableLoaderRows (internal)
// =============================================================================
@@ -260,13 +266,7 @@ function DataTableHeaders({ className }: { className?: string }) {
const handleClick = () => {
if (!isSortable || !onSort || !col.sort) return;
- const nextDirection =
- currentSort?.direction === "Asc"
- ? "Desc"
- : currentSort?.direction === "Desc"
- ? undefined
- : "Asc";
- onSort(col.sort.field, nextDirection);
+ onSort(col.sort.field, nextSortDirection(currentSort?.direction));
};
const align = resolveAlign(col);
diff --git a/packages/core/src/components/layout/Layout.tsx b/packages/core/src/components/layout/Layout.tsx
index 661179fb..29e8a433 100644
--- a/packages/core/src/components/layout/Layout.tsx
+++ b/packages/core/src/components/layout/Layout.tsx
@@ -216,16 +216,12 @@ export function Layout({ columns, className, style, gap, title, actions, childre
}
}
- const gapClass =
- gap === undefined
- ? "astw:gap-4"
- : gap === 4
- ? "astw:gap-4"
- : gap === 6
- ? "astw:gap-6"
- : gap === 8
- ? "astw:gap-8"
- : "astw:gap-4";
+ const GAP_CLASSES: Record = {
+ 4: "astw:gap-4",
+ 6: "astw:gap-6",
+ 8: "astw:gap-8",
+ };
+ const gapClass = gap === undefined ? "astw:gap-4" : (GAP_CLASSES[gap] ?? "astw:gap-4");
const hasLegacyHeader = !hasHeaderChild && (title || (actions != null && actions.length > 0));
diff --git a/packages/core/src/components/sidebar/default-sidebar.tsx b/packages/core/src/components/sidebar/default-sidebar.tsx
index de2c7109..414727e0 100644
--- a/packages/core/src/components/sidebar/default-sidebar.tsx
+++ b/packages/core/src/components/sidebar/default-sidebar.tsx
@@ -24,6 +24,15 @@ import { useT } from "@/i18n-labels";
import { useNavItems, type NavItem } from "@/routing/navigation";
import { cn } from "@/lib/utils";
+function resolveCollapsibleMode(
+ collapsible: boolean | undefined,
+ isIconMode: boolean,
+): "none" | "icon" | "offcanvas" {
+ if (!collapsible) return "none";
+ if (isIconMode) return "icon";
+ return "offcanvas";
+}
+
// Always rendered regardless of searchSources — the palette searches routes
// and contextual actions too, so there is always something to search.
const SearchEntry = () => {
@@ -102,11 +111,10 @@ export const DefaultSidebar = (props: DefaultSidebarProps) => {
);
const DefaultFooter = null;
+ const collapsibleMode = resolveCollapsibleMode(collapsible, isIconMode);
+
return (
-
+
{!isIconMode && (
{props.header ?? DefaultHeader}
diff --git a/packages/core/src/components/tabs.tsx b/packages/core/src/components/tabs.tsx
index 4afe385f..baa6e06d 100644
--- a/packages/core/src/components/tabs.tsx
+++ b/packages/core/src/components/tabs.tsx
@@ -5,6 +5,20 @@ import { cn } from "@/lib/utils";
type TabsVariant = "default" | "line" | "capsule";
+const LIST_VARIANT_CLASSES: Record = {
+ line: "astw:h-9 astw:gap-2",
+ capsule: "astw:h-10 astw:gap-0.5 astw:rounded-md astw:bg-muted astw:p-1",
+ default: "astw:text-muted-foreground astw:h-9 astw:gap-1",
+};
+
+const TAB_VARIANT_CLASSES: Record = {
+ line: "astw:px-3 astw:py-1.5 astw:-mb-px astw:border-b-2 astw:border-transparent astw:data-active:border-primary astw:data-active:text-foreground",
+ capsule:
+ "astw:rounded-md astw:px-3 astw:py-1.5 astw:data-active:bg-background astw:data-active:text-foreground astw:data-active:shadow-sm",
+ default:
+ "astw:rounded-md astw:px-3 astw:py-1 astw:data-active:bg-muted astw:data-active:text-foreground",
+};
+
const TabsVariantContext = React.createContext("default");
// Only the props relevant to the Tabs abstraction are picked from BaseTabs.Root.
@@ -55,11 +69,7 @@ function List({ className, children, ...props }: React.ComponentProps