@@ -3,15 +3,19 @@ import type { SubscriptionFormData } from '@/components/subscriptions/subscripti
33import { Badge } from '@/components/ui/badge'
44import { Button } from '@/components/ui/button'
55import { closestCenter , DndContext , DragEndEvent } from '@dnd-kit/core'
6+ import type { Modifier } from '@dnd-kit/core'
67import { rectSortingStrategy , SortableContext } from '@dnd-kit/sortable'
78import { FileText , Plus , RotateCcw } from 'lucide-react'
89import type { ComponentProps } from 'react'
10+ import { useMemo , useRef } from 'react'
911import type { FieldArrayWithId } from 'react-hook-form'
1012import { UseFormReturn } from 'react-hook-form'
1113import { useTranslation } from 'react-i18next'
1214
1315type DndContextSensors = NonNullable < ComponentProps < typeof DndContext > [ 'sensors' ] >
1416
17+ const clamp = ( value : number , min : number , max : number ) => Math . min ( Math . max ( value , min ) , max )
18+
1519export interface SubscriptionRulesSectionProps {
1620 form : UseFormReturn < SubscriptionFormData >
1721 ruleFields : FieldArrayWithId < SubscriptionFormData , 'rules' > [ ]
@@ -34,9 +38,28 @@ export function SubscriptionRulesSection({
3438 isSaving,
3539} : SubscriptionRulesSectionProps ) {
3640 const { t } = useTranslation ( )
41+ const rulesListRef = useRef < HTMLDivElement > ( null )
42+ const rulesModifiers = useMemo < Modifier [ ] > (
43+ ( ) => [
44+ ( { transform, draggingNodeRect } ) => {
45+ const listRect = rulesListRef . current ?. getBoundingClientRect ( )
46+ if ( ! draggingNodeRect || ! listRect ) {
47+ return { ...transform , x : 0 }
48+ }
49+ const edgeAllowance = clamp ( draggingNodeRect . height , 56 , 120 )
50+
51+ return {
52+ ...transform ,
53+ x : 0 ,
54+ y : clamp ( transform . y , listRect . top - draggingNodeRect . top - edgeAllowance , listRect . bottom - draggingNodeRect . bottom + edgeAllowance ) ,
55+ }
56+ } ,
57+ ] ,
58+ [ ] ,
59+ )
3760
3861 return (
39- < div className = "space-y-3" >
62+ < div className = "min-w-0 space-y-3 overflow-hidden " >
4063 < div className = "rounded-lg border bg-card p-3 shadow-sm sm:p-4" >
4164 < div className = "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4" >
4265 < div className = "min-w-0 flex-1 space-y-1" >
@@ -70,10 +93,10 @@ export function SubscriptionRulesSection({
7093 < p className = "mb-1 text-xs font-medium sm:text-sm" > { t ( 'settings.subscriptions.rules.noRules' ) } </ p >
7194 </ div >
7295 ) : (
73- < DndContext sensors = { sensors } collisionDetection = { closestCenter } onDragEnd = { onDragEnd } >
74- < div dir = "ltr" >
96+ < DndContext sensors = { sensors } collisionDetection = { closestCenter } modifiers = { rulesModifiers } onDragEnd = { onDragEnd } >
97+ < div dir = "ltr" className = "min-w-0 overflow-hidden" >
7598 < SortableContext items = { ruleFields . map ( field => field . id ) } strategy = { rectSortingStrategy } >
76- < div className = "scrollbar-thin flex max-h-[min(70vh,500px)] touch-pan-y flex-col gap-2 overflow-y-auto py-1 sm:max-h-[500px] sm:gap-2.5 sm:py-1" >
99+ < div ref = { rulesListRef } className = "scrollbar-thin flex max-h-[min(70vh,500px)] min-w-0 touch-pan-y flex-col gap-2 overflow-y-auto overflow-x-hidden py-1 sm:max-h-[500px] sm:gap-2.5 sm:py-1" >
77100 { ruleFields . map ( ( field , index ) => (
78101 < SortableSubscriptionRule key = { field . id } id = { field . id } index = { index } onRemove = { onRemoveRule } form = { form } />
79102 ) ) }
0 commit comments