Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/components/ui/DimConfig/DimConfigBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use client';
import { useState } from 'react';
import {
Axis,
canUseSliceMode,
getAvailableSliceAxes,
SelectionMode,
SliceSelectionState,
} from '@/components/ui/DimSlicer';
import { Button } from '@/components/ui/button';
import { DimConfigEntry } from './DimConfigEntry';

export interface DimConfigBarProps {
variableName: string;
dims: { name: string }[];
sels: SliceSelectionState[];
axes: Axis[];
onModeChange: (index: number, mode: SelectionMode) => void;
onAxisChange: (index: number, axis: Axis) => void;
}

const formatConfigToken = (selection: SliceSelectionState) => {
if (selection.mode === 'scalar') {
return selection.scalar || '0';
}
const start = selection.start || '0';
const stop = selection.stop || '';
if (start === '0' && stop === '') return ':';
return `${start}:${stop}`;
};

const AXIS_COLOR: Record<Axis, string> = {
x: 'text-pink-500',
y: 'text-green-600',
z: 'text-blue-500',
c: 'text-yellow-600',
};

export function DimConfigBar({
variableName,
dims,
sels,
axes,
onModeChange,
onAxisChange,
}: DimConfigBarProps) {
const [expanded, setExpanded] = useState(false);

return (
<div className="rounded-md border bg-muted/30 p-2.5">
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-1">
<div>
<span className="text-foreground">{variableName}</span>
<span className="text-muted-foreground">[</span>
{sels.map((selection, index) => {
if (!dims[index]) return null;
const tokenClass =
selection.mode === 'scalar'
? 'text-slate-500'
: AXIS_COLOR[axes[index]];
return (
<span key={index} className={tokenClass}>
{formatConfigToken(selection)}
{index < sels.length - 1 ? ', ' : ''}
</span>
);
})}
<span className="text-muted-foreground">]</span>
</div>
<div className="text-muted-foreground">Current slicing expression</div>
</div>

<Button
type="button"
size="sm"
variant="outline"
className="cursor-pointer"
aria-expanded={expanded}
onClick={() => setExpanded(prev => !prev)}
>
{expanded ? 'Hide config dimensions' : 'Config dimensions'}
</Button>
</div>

{expanded ? (
<div className="space-y-2">
{dims.map((dim, i) => (
<DimConfigEntry
key={i}
dimName={dim.name}
selection={sels[i]}
axis={axes[i]}
sliceAllowed={canUseSliceMode(sels, axes, i)}
availableAxes={getAvailableSliceAxes(sels, axes, i)}
onModeChange={mode => onModeChange(i, mode)}
onAxisChange={axis => onAxisChange(i, axis)}
/>
))}
</div>
) : null}
</div>
</div>
);
}
131 changes: 131 additions & 0 deletions src/components/ui/DimConfig/DimConfigEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
'use client';

import {
Axis,
SelectionMode,
SliceSelectionState,
} from '@/components/DimSlicer';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The import path for DimSlicer is incorrect. It is imported from '@/components/DimSlicer', but the actual path is '@/components/ui/DimSlicer'. This will cause a compilation/build error.

Suggested change
import {
Axis,
SelectionMode,
SliceSelectionState,
} from '@/components/DimSlicer';
import {
Axis,
SelectionMode,
SliceSelectionState,
} from '@/components/ui/DimSlicer';

import { Button } from '@/components/ui/button';
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from '@/components/ui/button-group';

import { cn } from '@/lib/utils';

const AXIS_OPTIONS: Axis[] = ['x', 'y', 'z', 'c'];

const AXIS_COLOR: Record<Axis, string> = {
x: 'text-pink-500',
y: 'text-green-600',
z: 'text-blue-500',
c: 'text-yellow-600',
};

const SELECTED_AXIS_BUTTON_CLASSES: Record<Axis, string> = {
x: 'text-white bg-pink-500 border-pink-500 hover:bg-pink-600',
y: 'text-white bg-green-600 border-green-600 hover:bg-green-700',
z: 'text-white bg-blue-500 border-blue-500 hover:bg-blue-600',
c: 'text-white bg-yellow-600 border-yellow-600 hover:bg-yellow-700',
};

const formatSelection = (sel: SliceSelectionState) =>
sel.mode === 'scalar'
? `[${sel.scalar}]`
: `[${sel.start}:${sel.stop}]`;

export interface DimConfigEntryProps {
dimName: string;
selection: SliceSelectionState;
axis: Axis;
sliceAllowed: boolean;
availableAxes: Axis[];
onModeChange: (mode: SelectionMode) => void;
onAxisChange: (axis: Axis) => void;
}

export function DimConfigEntry({
dimName,
selection,
axis,
sliceAllowed,
availableAxes,
onModeChange,
onAxisChange,
}: DimConfigEntryProps) {
const isSlice = selection.mode === 'slice';
const axisValue = availableAxes.includes(axis)
? axis
: (availableAxes[0] ?? axis);

return (
<ButtonGroup className="h-7">
<ButtonGroupText className="h-7 px-2">
{dimName}
</ButtonGroupText>

<ButtonGroupSeparator />

{sliceAllowed ? (
<Button
size="xs"
variant={isSlice ? 'default' : 'outline'}
className="cursor-pointer"
onClick={() => onModeChange('slice')}
aria-pressed={isSlice}
aria-label={`Slice mode for ${dimName}`}
>
slice
</Button>
) : null}

<Button
size="xs"
variant={!isSlice ? 'default' : 'outline'}
className="cursor-pointer"
onClick={() => onModeChange('scalar')}
aria-pressed={!isSlice}
aria-label={`Scalar mode for ${dimName}`}
>
scalar
</Button>

{isSlice ? (
<>
<ButtonGroupSeparator />
{AXIS_OPTIONS.map(a => {
const isDisabled =
a === 'c' || !availableAxes.includes(a);
const isSelected = axisValue === a;

return (
<Button
key={a}
size="xs"
variant={isSelected ? 'default' : 'outline'}
disabled={isDisabled}
className={cn(
'min-w-6 px-1.5 cursor-pointer',
isSelected ? SELECTED_AXIS_BUTTON_CLASSES[a] : undefined,
isSelected && !isDisabled && 'shadow-sm'
)}
onClick={() => onAxisChange(a)}
aria-pressed={isSelected}
aria-label={`Axis ${a} for ${dimName}`}
>
{a}
</Button>
);
})}
</>
) : null}

<ButtonGroupSeparator />

<ButtonGroupText className="px-2 text-muted-foreground">
{formatSelection(selection)}
</ButtonGroupText>
</ButtonGroup>
);
}
2 changes: 2 additions & 0 deletions src/components/ui/DimConfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DimConfigBar } from './DimConfigBar';
export { DimConfigEntry } from './DimConfigEntry';
Loading
Loading