feat(admin) : add sortable condition defaults
This commit is contained in:
@@ -1128,6 +1128,140 @@ function SortableExtraItem({
|
||||
);
|
||||
}
|
||||
|
||||
type ConditionNavItem = {
|
||||
clientId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
function SortableConditionTab({
|
||||
item,
|
||||
index,
|
||||
active,
|
||||
onSelect,
|
||||
onRename,
|
||||
}: {
|
||||
item: ConditionNavItem;
|
||||
index: number;
|
||||
active: boolean;
|
||||
onSelect: () => void;
|
||||
onRename: () => void;
|
||||
}) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: item.clientId });
|
||||
const label = item.name.trim() || `Condition ${index + 1}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{ transform: CSS.Transform.toString(transform), transition }}
|
||||
className={cn("flex shrink-0 items-center rounded-md border", isDragging && "z-10 opacity-60")}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value={item.clientId}
|
||||
className="border-0 aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
||||
onClick={onSelect}
|
||||
onDoubleClick={onRename}
|
||||
>
|
||||
{label}
|
||||
{index === 0 ? <Badge variant={active ? "secondary" : "outline"}>Default</Badge> : null}
|
||||
</ToggleGroupItem>
|
||||
<SimpleTooltip text={`ลากเพื่อจัดลำดับ ${label}`}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="touch-none rounded-l-none"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<GripVerticalIcon />
|
||||
<span className="sr-only">ลากเพื่อจัดลำดับ {label}</span>
|
||||
</Button>
|
||||
</SimpleTooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SortableConditionSwitcher({
|
||||
ariaLabel,
|
||||
items,
|
||||
activeIndex,
|
||||
onActiveIndexChange,
|
||||
onReorder,
|
||||
onRename,
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
items: ConditionNavItem[];
|
||||
activeIndex: number;
|
||||
onActiveIndexChange: (index: number) => void;
|
||||
onReorder: (from: number, to: number) => void;
|
||||
onRename: (index: number) => void;
|
||||
}) {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
||||
);
|
||||
const activeId = items[activeIndex]?.clientId;
|
||||
|
||||
return (
|
||||
<ScrollableNav ariaLabel={ariaLabel} className="min-w-0 flex-1">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={({ active, over }) => {
|
||||
if (!over || active.id === over.id) return;
|
||||
const from = items.findIndex((item) => item.clientId === active.id);
|
||||
const to = items.findIndex((item) => item.clientId === over.id);
|
||||
if (from !== -1 && to !== -1) onReorder(from, to);
|
||||
}}
|
||||
>
|
||||
<SortableContext items={items.map((item) => item.clientId)} strategy={rectSortingStrategy}>
|
||||
<ToggleGroup
|
||||
aria-label={ariaLabel}
|
||||
variant="outline"
|
||||
value={activeId ? [activeId] : []}
|
||||
onValueChange={(values) => {
|
||||
const index = items.findIndex((item) => item.clientId === values[0]);
|
||||
if (index !== -1) onActiveIndexChange(index);
|
||||
}}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<SortableConditionTab
|
||||
key={item.clientId}
|
||||
item={item}
|
||||
index={index}
|
||||
active={item.clientId === activeId}
|
||||
onSelect={() => onActiveIndexChange(index)}
|
||||
onRename={() => onRename(index)}
|
||||
/>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</ScrollableNav>
|
||||
);
|
||||
}
|
||||
|
||||
function reorderConditions<T extends ConditionNavItem>(
|
||||
items: T[],
|
||||
activeIndex: number,
|
||||
from: number,
|
||||
to: number,
|
||||
) {
|
||||
const activeId = items[activeIndex]?.clientId;
|
||||
const reordered = arrayMove(items, from, to);
|
||||
return {
|
||||
items: reordered,
|
||||
activeIndex: Math.max(0, reordered.findIndex((item) => item.clientId === activeId)),
|
||||
};
|
||||
}
|
||||
|
||||
export function GuideSectionEditor(props: GuideSectionEditorProps) {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
@@ -1249,27 +1383,18 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
||||
/>
|
||||
</Field>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ScrollableNav ariaLabel="เลือกเงื่อนไขอาวุธ" className="min-w-0 flex-1">
|
||||
<ToggleGroup
|
||||
aria-label="เลือกเงื่อนไขอาวุธ"
|
||||
variant="outline"
|
||||
value={[String(activeGroupIndex)]}
|
||||
onValueChange={(values) => {
|
||||
if (values[0]) setActiveGroupIndex(Number(values[0]));
|
||||
}}
|
||||
>
|
||||
{groups.map((group, index) => (
|
||||
<ToggleGroupItem
|
||||
key={group.clientId}
|
||||
value={String(index)}
|
||||
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
||||
onDoubleClick={() => setRenamingWeaponGroupIndex(index)}
|
||||
>
|
||||
{group.name.trim() || `Condition ${index + 1}`}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</ScrollableNav>
|
||||
<SortableConditionSwitcher
|
||||
ariaLabel="เลือกเงื่อนไขอาวุธ"
|
||||
items={groups}
|
||||
activeIndex={activeGroupIndex}
|
||||
onActiveIndexChange={setActiveGroupIndex}
|
||||
onRename={setRenamingWeaponGroupIndex}
|
||||
onReorder={(from, to) => {
|
||||
const reordered = reorderConditions(groups, activeGroupIndex, from, to);
|
||||
setGroups(reordered.items);
|
||||
setActiveGroupIndex(reordered.activeIndex);
|
||||
}}
|
||||
/>
|
||||
<Button type="button" variant="outline" disabled={groups.length >= 20} onClick={() => {
|
||||
setGroups([...groups, { clientId: createClientId("weapon-condition"), name: "", note: "", rows: [] }]);
|
||||
setActiveGroupIndex(groups.length);
|
||||
@@ -2108,17 +2233,18 @@ function ArtifactEditor({
|
||||
<FieldSet>
|
||||
<FieldLegend>Target Stats</FieldLegend>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ScrollableNav ariaLabel="เลือกเงื่อนไขค่าสถานะเป้าหมาย" className="min-w-0 flex-1">
|
||||
<ToggleGroup aria-label="เลือกเงื่อนไขค่าสถานะเป้าหมาย" variant="outline" value={[String(activeTargetGroupIndex)]} onValueChange={(values) => {
|
||||
if (values[0]) setActiveTargetGroupIndex(Number(values[0]));
|
||||
}}>
|
||||
{targetGroups.map((group, index) => (
|
||||
<ToggleGroupItem key={group.clientId} value={String(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground" onDoubleClick={() => setRenamingTargetGroupIndex(index)}>
|
||||
{group.name.trim() || `Condition ${index + 1}`}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</ScrollableNav>
|
||||
<SortableConditionSwitcher
|
||||
ariaLabel="เลือกเงื่อนไขค่าสถานะเป้าหมาย"
|
||||
items={targetGroups}
|
||||
activeIndex={activeTargetGroupIndex}
|
||||
onActiveIndexChange={setActiveTargetGroupIndex}
|
||||
onRename={setRenamingTargetGroupIndex}
|
||||
onReorder={(from, to) => {
|
||||
const reordered = reorderConditions(targetGroups, activeTargetGroupIndex, from, to);
|
||||
setTargetGroups(reordered.items);
|
||||
setActiveTargetGroupIndex(reordered.activeIndex);
|
||||
}}
|
||||
/>
|
||||
<ConditionRenameDialog
|
||||
open={renamingTargetGroupIndex !== null}
|
||||
fallback={`Condition ${(renamingTargetGroupIndex ?? 0) + 1}`}
|
||||
@@ -2417,27 +2543,18 @@ function ConstellationEditor({
|
||||
/>
|
||||
</Field>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ScrollableNav ariaLabel="เลือกเงื่อนไขกลุ่มดาว" className="min-w-0 flex-1">
|
||||
<ToggleGroup
|
||||
aria-label="เลือกเงื่อนไขกลุ่มดาว"
|
||||
variant="outline"
|
||||
value={[String(activeConstellationGroupIndex)]}
|
||||
onValueChange={(values) => {
|
||||
if (values[0]) setActiveConstellationGroupIndex(Number(values[0]));
|
||||
}}
|
||||
>
|
||||
{groups.map((group, index) => (
|
||||
<ToggleGroupItem
|
||||
key={group.clientId}
|
||||
value={String(index)}
|
||||
className="aria-pressed:bg-primary aria-pressed:text-primary-foreground"
|
||||
onDoubleClick={() => setRenamingConstellationGroupIndex(index)}
|
||||
>
|
||||
{group.name.trim() || `Condition ${index + 1}`}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</ScrollableNav>
|
||||
<SortableConditionSwitcher
|
||||
ariaLabel="เลือกเงื่อนไขกลุ่มดาว"
|
||||
items={groups}
|
||||
activeIndex={activeConstellationGroupIndex}
|
||||
onActiveIndexChange={setActiveConstellationGroupIndex}
|
||||
onRename={setRenamingConstellationGroupIndex}
|
||||
onReorder={(from, to) => {
|
||||
const reordered = reorderConditions(groups, activeConstellationGroupIndex, from, to);
|
||||
setGroups(reordered.items);
|
||||
setActiveConstellationGroupIndex(reordered.activeIndex);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
@@ -3110,27 +3227,24 @@ function TeamEditor({
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<ScrollableNav ariaLabel="เลือกเงื่อนไข Team" className="min-w-0 flex-1">
|
||||
<ToggleGroup
|
||||
aria-label="เลือกเงื่อนไข Team"
|
||||
variant="outline"
|
||||
value={[String(activeTeamGroupIndex)]}
|
||||
onValueChange={(values) => {
|
||||
const nextIndex = Number(values[0]);
|
||||
const group = teamGroups[nextIndex];
|
||||
if (!group) return;
|
||||
setActiveTeamGroupIndex(nextIndex);
|
||||
setSelectedTeamIds(new Set());
|
||||
setSourceTeamId(group.teams[0]?.clientId ?? "");
|
||||
}}
|
||||
>
|
||||
{teamGroups.map((group, index) => (
|
||||
<ToggleGroupItem key={group.clientId} value={String(index)} onDoubleClick={() => setRenamingTeamGroupIndex(index)} className="aria-pressed:bg-primary aria-pressed:text-primary-foreground">
|
||||
{group.name.trim() || `Condition ${index + 1}`}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</ScrollableNav>
|
||||
<SortableConditionSwitcher
|
||||
ariaLabel="เลือกเงื่อนไข Team"
|
||||
items={teamGroups}
|
||||
activeIndex={activeTeamGroupIndex}
|
||||
onActiveIndexChange={(nextIndex) => {
|
||||
const group = teamGroups[nextIndex];
|
||||
if (!group) return;
|
||||
setActiveTeamGroupIndex(nextIndex);
|
||||
setSelectedTeamIds(new Set());
|
||||
setSourceTeamId(group.teams[0]?.clientId ?? "");
|
||||
}}
|
||||
onRename={setRenamingTeamGroupIndex}
|
||||
onReorder={(from, to) => {
|
||||
const reordered = reorderConditions(teamGroups, activeTeamGroupIndex, from, to);
|
||||
setTeamGroups(reordered.items);
|
||||
setActiveTeamGroupIndex(reordered.activeIndex);
|
||||
}}
|
||||
/>
|
||||
<ConditionRenameDialog
|
||||
open={renamingTeamGroupIndex !== null}
|
||||
fallback={`Condition ${(renamingTeamGroupIndex ?? 0) + 1}`}
|
||||
|
||||
Reference in New Issue
Block a user